j2SE學習總結

J2se學習總結
                     Allan  2005-12
 
Java的一些規定
 
1、若在源文件中定義了聲明爲public的類,需要將類所在的源文件的文件名取名爲類名
2、在同一個源文件中有且只能有一個定義爲public的類
3、編譯時文件名大小寫是不敏感的,執行的時候加載的類名是大小寫敏感的
 
Java的語法
 
1、byte類型是一個有符號的8位的整數(-128~127)。其他語言的字節類型通常是無符號的整數。
2、爲了保持精度,byte型與byte型或整數運算時,將轉換爲整型後運算。將結果賦值給byte變量需要類型轉換。
3、short類型(-32768~32767)
4、java中的char類型可以表示0~65535個字符,利用unicode編碼格式
5、可以使用單引號字符或者整數對char型賦值
6、java中小數常量被認爲double型,若要表明爲float型,在其後加f
7、float是4個字節,double是8個字節
8、java中的boolean只有兩個取值true和false
9、java中條件判斷只能使用true或者false
 
java中的數組
 
1、java中一維數組在定義的時候是不能夠分配空間的,例:
       int num[];//中括號中不能寫大小
   只有在定義完成之後爲數組分配大小
       num = new int[3];
   java中數組定義建議採取下面的形式:
       int[] num;
   數組可以在定義時進行初始化:
       int[] num = {1, 2, 3};
   或者
       int[] num = new int[]{1, 2, 3};
   注意不要寫爲:
       int[] num = new int[3]{1, 2, 3};
2、java中的二維數組
   定義:
       int[][] num;
   分配空間:
       num = new int[1][2];
3、java中二維數組每行的列數可以不相同:
       int[][] num;
       num = new int[3][];
       num[0] = new int[1];
       num[1] = new int[2];
       num[2] = new int[3];
   有些像c/c++中的指針數組
4、java中定義一個數值型數組的時候,會自動將數組的元素全部賦值爲0
5、當用初始值填充數組時,不要在中括號中填寫大小,例:
       int[][] num = new int[2][]{{1, 2, 3}, {4, 5, 6}}; //error
6、java支持不規則數組元素
       int[][] num = {{1, 2, 3}, {4, 5}, {6}};
  
java中的自增操作
 
1、表現形式與c/c++中一樣
 
java的移位運算符
 
1、左移<<
   帶符號右移>>
   無符號右移>>>
 
java的包
 
1、package語句必須是java源文件中的第一條語句。如果不加package語句,則指定爲缺省包或者無名包。
2、包對應着文件系統的目錄層次結構。在package語句中,用.來指明包(目錄)的層次。
   如果在源文件中定義了package(給類取了包名),則類的完整的名字爲:包名.類名 。
3、java中提供的包(package)和文件系統的目錄層次結構是相對應的。當爲類定義了包名時,  
   要求在硬盤上有相應包名的目錄,該目錄下有類文件。
4、可以使用/分隔包名和類名,通常使用.
5、包名可以使用多重限定名,例:package p1.p2.p3;//即頂層包p1有子包p2,p2有子包p3,包 
   p3中有當前類。注意:文件系統中應該有相應的目錄層次結構。
6可以使用javac的-d參數指定在什麼位置生成類的文件,並且會根據源文件中定義的包名生成相應的目錄層次結構。未指定-d則在當前目錄生成類文件,並且不會生成package指定的目錄
層次。
 
import語句
 
1、引入包中的類:import java.io.File;
2、引入整個包: import java.io.*;
3在同一包中的類可以相互引用,無需import語句。
4、java.lang包是自動導入的,不需要顯式的加上import語句。
5除非有必要儘量避免導入一個包中的所有類。
 
類的說明符
 
1、類的訪問說明符
   (1)public
   (2)default(不加訪問說明符時)
2、類的其它修飾符
   (1)final     表明類爲最終類,不能派生其它子類
   (2)abstract 抽象類
3將目錄下的所有源文件都編譯生成:javac -d . *.java
   好處是不需要考慮哪個類先生成了。
4、class關鍵字前沒有加任何訪問說明符時,類爲缺省類。不同包中的類無法訪問。
5、在不同的包之間訪問類時,只能訪問到包中聲明爲public的類。
6、缺省的類訪問說明符,表明類只能被同一包中的類訪問。
 
方法的訪問說明符
 
1、方法的訪問說明符
   (1)public                   
   (2)protected               
   (3)default(不加訪問說明符時)
   (4)private
       
              public    protected    default        private
    同類        v               v           v             v
    同包        v               v           v
    子類        v               v
    不同包      v
       
2、方法的其它修飾符
   (1)static
   (2)final
   (3)abstract
   (4)native
   (5)synchronized
3final方法:爲了確保某個函數的行爲在繼承過程中保持不變,並且不能被覆蓋
   (overridden),可以使用final方法。
4、抽象的方法和抽象類:
   *在類中沒有方法體的方法就是抽象方法。
   *含有抽象方法的類,即爲抽象類。
   *如果一個子類沒有實現抽象基類中所有的抽象方法,則子類也成爲一個抽象類。
   *我們可以將一個沒有任何抽象方法的類聲明爲abstract,避免由這個類產生任何的對象。
   *抽象類需要聲明abstract
 
垃圾回收
 
1、java虛擬機退出之前,會去調用函數finalize()
2、java虛擬機中,垃圾回收是作爲一個低優先級的線程在運行。在內存不夠的情況下,纔會運行垃圾收集器。
3、使用System的靜態方法gc()運行垃圾回收器。
 
接口
 
1、接口中所有的方法都是抽象的,定義了一類行爲的集合,行爲的實現由其實現類來完成。
2、使用interface定義接口,而不是class。
3、類使用implements關鍵字實現接口。
4、接口中所有的方法都是public abstract。
5、實現一個接口時,如果類需要實例話,則要實現接口中所有的方法。
6、接口不能實例化爲一個對象,但實現了接口的類可以作爲接口的實例。
7、在接口中聲明方法時,不能使用native、static、final、synchronized、private、
   protected等修飾符。
8、和public類一樣,public接口也必須定義在與接口同名的文件中。
9、接口中可以有數據成員,這些成員默認都是public static final。
10、訪問接口的數據成員的幾種形式:
        接口.接口數據成員名稱
        實現了接口的類的名稱.接口數據成員名稱
        實現了接口的類的實例.接口數據成員名稱
11、一個接口可以繼承自另一個接口。
12java中不允許類的多繼承,但允許接口的多繼承。
13、在java中,一個類可以實現多個接口。
14、一個類在繼承另外一個類的同時,可以實現多個接口。
 
內部類
 
1、內部類:在一個類中定義另外一個類,這個類就叫做內部類或內置類(inner class)。
2、內部類可以讓我們將邏輯上相關的一組類組織起來,並由外部類(outer class)來控制內部類的可見性。
3、當我們建立一個inner class時,其對象就擁有了與外部類對象之間的一種關係,這就是通過一個特殊的this reference形成的,使得內部類對象可以隨意的訪問外部類中的所有的成員。
4、內部類可以隨意的訪問外部類中所有的成員方法和成員變量(包括私有的成員方法和成員變
   量)。
5、java中凡是用new產生的對象,都是在堆內存中分配的。
6、在內部類中引用外部成員變量可以使用以下形式:
   外部類名.this.成員變量
   例:outer.this.varname
7、若main函數不在外部類中,引用外部類中的內部類,如聲明一個內部類,形式如下:
       Outer.Inner inner;
8、如果想要直接實例化一個內部類對象,必須要有一個引用指向外部類對象
   如何在main方法中直接產生內部類對象:
       Outer.Inner inner = outer.new Inner();
9、內部類定義可以放在函數、條件語句、語句塊中,並且不管內部類嵌套的層次有多深,都可以訪外部類的成員。
10、若方法的內部有內部類,方法定義的局部變量需要被內部類所訪問,則需要將變量聲明爲final 。
11、對於一個類,訪問權限可以是缺省的或者是public的。而對於一個內部類,還也可以聲明爲
    protected 或者 private、abstract、final、static 。如果內部類聲明爲
    abstract,則內部類就不能實例化了。需要在外部類中再定義一個內部類,從聲明爲
    abstract的類派生出來,然後再實例化。一個靜態的內部類,只能訪問外部類中靜態
    的成員變量或者一個靜態的成員方法。
12、靜態的內部類可以有靜態的成員函數或變量,非靜態內部類中不能有靜態的聲明。
13、在方法中定義的內部類,如果要訪問方法中定義的本地變量或者方法的參數,則變量必須被聲明爲final。
14、內部類可以聲明爲private或protected;還可以聲明爲abstract或final。
15、內部類可以聲明爲static的,但此時就不能再使用外部類的非static的成員變量和非static的成員方法;
16、非static的內部類中的成員不能聲明爲static的,只有在頂層類或static的內部類中纔可以聲明static成員。
17、產生一個派生類對象的時候,會調用基類的構造函數,產生一個基類的對象。
18要產生一個內部類對象,首先需要產生一個外部類的對象,然後才能產生內部類對象。從而建立起內部類對象到外部類對象的一個引用關係。
19、爲什麼使用內部類?
    *在內部類(inner class)中,可以隨意的訪問外部類成員,這可以讓我們更好地組織管理我們的代碼,增強代碼的可讀性。
    *內部類可以用於創建適配器類,適配器類是用於實現接口的類。使用內部類來實現接口,可以更好地定位與接口關聯的方法在代碼中的位置。
 
java中的異常處理
 
1、java通過異常類表明異常,所有的異常都有個叫做Exception的基類。
2、派生自Exception的異常類,不一定都在java.lang包中。
3、Exception類派生自java.lang.Throwable,從Throwable派生了2個類:
   *Error類:從該類派生的有VitualMachineError(包含了OutOfMemoryError和
             StackOverflowError)和AWTError
   *Exception:從該類派生的有RuntimeException和IOException等
              RuntimeException通常代表了程序編寫時的一些錯誤,這類錯誤在java中不需
                              要捕獲,由java運行時系統自動拋出並自動處理。
4、打開一個不存在的文件、網絡連接中斷、數組下標越界、正在加載的類文件丟失等都會引發
   異常。
5、java中的異常類定義了程序中遇到的輕微的錯誤條件。
6、java中的錯誤類定義了程序中不能恢復的嚴重錯誤條件。如內存溢出、類文件格式錯誤等。
   這一類錯誤由java運行系統處理,不需要我們去處理。
7、java程序在執行過程中如出現異常,會自動生成一個異常類對象,該異常對象將被提交給
   java運行時系統,這個過程稱爲拋出(throw)異常。
8、當java運行時系統接收到異常對象時,會尋找能處理這一異常的代碼並把當前異常對象交給
   其處理,這一過程稱爲捕獲(catch)異常。
9、如果java運行時系統找不到可以捕獲異常的方法,則運行時系統將終止,相應的java程序也將退出。
10、try/catch/finally語句。
11、一旦引發了異常,調用方法下面的語句就不會再執行了。
12、Exception的方法來自其基類Throwable。
    幾個主要的方法:
    getMessage()
    toString()
    printStackTrace()
13、告訴用戶使用自己寫的方法調用時有可能產生異常,讓其做好準備,可以使用java的異常聲明語法去聲明一個異常,在方法的參數列表後面使用throws關鍵字拋出一個異常。當我們聲明要拋出一個異常,java編譯器在編譯的時候就會強制我們去捕獲這個異常。
14、拋出一個異常的時候,會拋給它的調用者。
    main函數可以將異常拋給java運行時系統。
15、實際編寫代碼的時候,最好將自行捕獲到,在自己的代碼中進行處理,打印出一個準確而又
    詳盡的信息提示給用戶,或者對發生的異常進行補救。
16、所有的異常都是Exception所派生出來的,所以寫異常捕獲代碼時一般將具體的異常捕獲放
    在前面,將通用型的放在後面。也就是說由特殊到一般的寫catch語句。
17、除了RuntimeException這類異常之外的異常,當我們拋出的時候java編譯器都會強制的要
    求我們去進行捕獲。
18、在catch到異常後,可以不進行處理,將異常對象再次拋出:
           throw e;
19、throw和throws的區別:
    *當我們聲明拋出異常的時候,使用throws關鍵字;
    *當我們拋出異常的時候,使用throw關鍵字。
20、程序執行到throw語句,程序將會發生跳轉。
21、可以拋出一個新的異常:
          throw new Exception("new exception");
22、定義自己的異常類:
    *派生自Exception;
    *構造方法;
    *構造方法中調用基類的構造方法;(super())
23、聲明異常的時候,可以同時聲明拋出多個異常。
24、不管程序執行有沒有發生異常,finally語句中的代碼都會執行(在return前也會執行)。
25、當程序終止運行的時候,finally語句就不會執行了。
26、System.exit(int status)靜態方法終止當前正在運行的java虛擬機。其參數status表示
    狀態的代碼,按照約定,一個非0的狀態代碼指示了一個非正常的終止。
 
java編程規範
 
1、package的命名
   package的名字由全部小寫的字母組成,例如:cn.mypackage
2、class和interface的命名
   class和interface的名字由大寫字母開頭而其它字母都小寫的單詞組成,例如:
   Exception、RuntimeException。
3、class變量的命名
   變量的名字用一個小寫字母開頭,後面的單詞用大寫字母開頭,例如:
   myInfocurrentUser
4class方法的命名
   方法的名字用一個小寫字母開頭,後面的單詞用大寫字母開頭,例如:
   run()、getInstance()
5、static final常量的命名
   所有字母都大寫,並且能表示完整含義。例如:PI
6、參數的命名
   參數的名字和變量的命名規範一致。
7、數組的命名
   數組應該總是用這樣的方式命名:byte[] buffer
 
java的常用包
 
1、java.applet:包含一些用於創建java小應用程序的類。
2、java.awt:   包含一些用於編寫與平臺無關的圖形界面(GUI)應用程序的類。
3、java.io:    包含一些用作輸入輸出(I/O)處理的類。
4、java.lang: 包含一些java語言的基本類與核心類,如String、Math、Integer、System
               和Runtime,提供常用的功能,這個包中的所有類是被隱式導入的。
5、java.net:   包含用於建立網絡連接的類,與java.io同時使用完成與網絡有關的讀寫。
6、java.util:包含一些實用工具類和數據結構類。
 
==equals的用法
 
1在java中,boolean、byte、short、int、long、char、float、double這八種是基本數據
   類型,其餘的都是引用類型
2==是比較兩個變量的值是否相等
    "equals"是比較兩個對象變量所代表的對象的內容是否相等
3、當我們聲明瞭一個引用類型變量時,系統只爲該變量分配了引用空間,並未創建一個具體的
   對象;當用new爲對象分配空間後,將對象的引用賦值給引用變量。
   *可以將java中的引用理解爲一個地址,也就是對象的首地址
 
String和StringBuffer
 
1、String str = "abc";
   int i = 3;
   float f = 4.5f;
   char ch = 'a';
   boolean b = true;
   System.out.println(str + i + f + ch + b);
2、針對String的++=,是java中唯一被重載的操作符;在java中,不允許程序員重
   載操作符。
3、String類對象是一個常量對象。(String對象一旦給賦了一個引用之後,它就是一個常量對
   象了)
   String str = "abc";
   str = "def";       //"abc"此時成了垃圾內存,str保存了"def"的引用
4、在處理大量字符串的程序中,我們通常用StringBuffer來替代String。
5、構造一個空的StringBuffer,初始的容量是16個字符。
        StringBuffer()
   超出16個字符,容量會自動增加,這樣就不用擔心容量不夠。
        StringBuffer(int length);
   構造時指定初始容量。
  
   StringBuffer類的一些常用方法:
      int capacity() 返回當前StringBuffer的容量;
      char charAt() 獲取一個char;
      StringBuffer delete(int start, int end) 刪除StringBuffer中字符串的一個子
                                               串,從開始位置到結束位置。
                                               需要注意的是,刪除的結束位置字符通
                                               常是不包括的。可以理解爲大於等於起
                                               始位置和小於結束位置。
      StringBuffer reverse()
 
數組
 
1、數組元素是基本數據類型將被初始化爲0,是引用類型的將被初始化爲NULL
2、當給數組變量賦值爲NULL時,相當於把它所保存的引用清除掉了。原來保存的引用的所在的
   對象就成了垃圾對象。所以,如果想讓內存成爲垃圾內存,可以給保存了對象引用的變量賦
   值爲NULL。
3、main方法是由java虛擬機調用的,所以必須是public的。由於java調用main方法時,不需要
   產生任何的對象,所以它要聲明爲static。不需要返回值,所以聲明爲void。有一個引用類
   型的數組參數String[] args。
   *args是用來接收命令行參數的,不包括命令行輸入的java和java後的類名稱。
  
函數的調用
 
1、在java中,傳遞參數時,都是以傳值的方式進行。
2、對於基本數據類型,傳遞的是數據的拷貝;對於引用類型,傳遞的是引用的拷貝。
 
 
數組的相關操作
 
1、在java中,所有的數組都有一個缺省的屬性length,用於獲取數組中元素的個數。
2、數組的複製:System.arraycopy(
                 Object src,
                 int srcPos,
                 Object dest,
                 int destPos,
                 int length)
3、數組的排序:Arrays.sort()。
4、在已排序的數組中查找某個元素:
   Arrays.binarySearch()
5、Arrays類在java.util包中。
6、數組是一種引用類型的變量。
7、Arrays.sort()對於對象排序的要求:數組中的所有元素必須實現了Comparable接口。數組中的所有元素都是可以互相比較的。
   Comparable接口中有一個方法需要被實現:
      public int compareTo(Object o);
      //小於返回負數,等於返回0,大於返回正數。
8、String類本身已經實現了Comparable接口,可以直接調用compareTo進行比較。
 
封裝類
 
1、針對八種基本數據類型定義的相應的引用類型--封裝類:
  
    基本數據類型    封裝類
    boolean     Boolean
    byte            Byte
    short           Short
    int         Integer
    long            Long
    char            Character
    float           Float
    double          Double
2、從Integer中取出int值使用函數intValue();
   從Long中取出long值使用函數longValue()
   從Float中取出float值使用函數floatValue();
3、將Integer轉換爲字符串類型toString()
4、將String類型轉換爲Integer可以用Integer.valueOf(String s)靜態方法。
5、基本數據類型轉換爲String可以通過是使用封裝類型的toString()方法達到目的。
6、將String類型轉換爲基本數據類型可以通過調用封裝類的parseXXX()靜態方法來達到目
   的。例:int n = Integer.parseInt("123");
          float f = Float.parseFloat("12.34");
   注意Boolean類型沒有類似的parse用法。
7、所有的封裝類都是隻讀類型沒有提供任何方法修改其內容。
 
 
程序、進程和線程
 
1、程序是計算機指令的集合,它以文件的形式存儲在磁盤上。
   進程:是一個程序在其自身的地址空間中的一次執行活動。
   *進程是資源申請、調度和獨立運行的單位,因此,它使用系統中的運行資源;而程序不能申
    請系統資源,不能被系統調度,也不能作爲獨立運行的單位,因此,它不佔用系統的運行資
    源。
   線程:是進程中的一個單一的連續控制流程。一個進程可以擁有多個線程。
   *線程又成爲輕量級進程,它和進程一樣擁有獨立的執行控制,由操作系統負責調度,區別在
    於線程沒有獨立的存儲空間,而是和所屬進程中的其它線程共享一個存儲空間,這使得線程
    間的通信遠較進程簡單。
2、一個進程中至少會有一個線程。
 
java對多線程的支持
 
1、java在語言級提供了對多線程程序設計的支持。
2、實現多線程程序的兩種方式:
   (1)從Thread類繼承;
   (2)實現Runnable接口。
3、一個Thread對象就代表了進程當中的一個線程。java虛擬機允許一個應用程序可以擁
有多個併發運行的線程。當一個JVM啓動的時候,通常由一個單一的非後臺線程(典
型的就是調用main方法的線程)
4、可以利用線程類當中的靜態方法
   static Thread currentThread()返回當前正在執行的Thread對象的引用,也就是獲取
當前線程。
5、可以利用線程類當中的方法
    String getName()獲取線程的名字。
   例:Thread.currentThread().getName();
6、讓線程可以運行我們的代碼需要覆蓋其run()方法。
7、讓線程運行需要調用線程類中的一個方法start。當我們調用start方法的時候就會導
致這個線程開始執行,然後java的虛擬機就會調用這個線程的run方法。可以把run
方法理解爲線程的入口函數。
8、可以把一個線程設置爲後臺線程,利用線程類中的一個方法:
   void setDaemon(boolean on)標記這個線程爲後臺線程或者用戶線程。
   如果參數設置爲真,標記該線程爲後臺線程。
      *main方法所在的線程是非後臺線程。
9、一個正在執行的線程可以放棄它執行的權利讓另一個線程運行,使用Thread類中的靜態方法 static void yield()導致當前正在執行的thread對象臨時暫停允許其它thread執行。
10、java中每一個線程都有一個優先級,可以通過Thread類中的方法int getPriority()
和void setPriority(int newPriority)去得到一個線程的優先級和設置線程的優先級。
    *線程優先級的取值範圍(1~10):
     三個常量(MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY)表示線程最大、最小、
默認優先級,它們表示的整數值分別是10、1、5。
    *可以隨時去設置線程的優先級,可以在start()前也可以在start()後去修改。
    *java中如果一個線程的優先級較高,那麼它將始終獲得運行的機會
 
java多線程的支持
 
1、java運行時系統實現了一個用於調度線程執行的線程調度器,用於確定某一時刻由哪一個線程在CPU上運行。
2、在java技術中,線程通常是搶佔式的而不需要時間片分配進程(分配給每個線程相等CPU時間的進程)。搶佔式調度模型就是許多線程處於可以運行狀態(等待狀態),但實際上只有一個線程在運行。該線程一直運行到它終止進入可運行狀態(等待狀態),或者另一個具有更高優先級的線程可變成可運行狀態。在後一種情況下,低優先級的線程被高優先級的線程搶佔,高優先級的線程獲得運行的機會。(程序編寫中,不要利用高優先級將始終運行這個特點來完成某些功能)
 
3、java線程調度器支持不同優先級線程的搶先方式,但其本身不支持相同優先級線程的
時間片輪轉。
4、java運行時系統所在的操作系統支持時間片的輪轉,則線程調度器就支持相同優先級線程的時間片輪換。
 
 
實現線程的第二種方式(實現Runnable接口)
 
1、 Thread類其實也實現了Runnable接口
2、 對於一個類來說,如果說它的實例想要被一個線程去執行,就應該實現Runnable接口。該接口只有一個方法run()。
3、 構造Thread的時候可以傳遞一個實現了Runnable接口的對象:
Thread(Runnable target)作爲Thread構造函數的參數。Java虛擬機會調用實現了Runnable接口對象當中的run()方法來執行我們的代碼。
 
MyThread mt = new MyThread();
New Thread(mt).start();
4、 通常,如果不需要修改線程類當中除了run()方法之外的其它方法的行爲之外,最好都
是要去實現Runnable接口。
5、 實現Runnable接口有兩個好處:
*已經繼承了其他類無法再繼承Thread類時,就實現Runnable接口;
*如果多個線程訪問同一種資源的話是很方便的:
6、Thread.sleep(long millis)讓當前正在執行的線程睡眠一會。
7、 讓一個線程進入一段代碼後即使休眠了其它線程也不能進入這段代碼,除非它將剩餘的代碼執行完成之後,其它的線程才能後進入到這段代碼之中。可以利用java語言中的同步來完成。
 
 
線程的同步
 
1、在一個程序中,代碼段訪問了同一個對象從單獨的、併發的線程當中,這個代碼段就
叫做“臨界區”。採用同步的機制對臨界區進行保護。
2、同步的兩種方式:同步塊和同步方法。這兩種方式都是通過一個synchronized關鍵字
來完成的。
A.對於同步塊來說,在synchronized後面需要加上一個對象,這個對象是任意的一個
對象。可以定義一個String類型的,也可以定義一個Object類型的對象:
Object obj = new Object();
在synchronized後面加上這個對象:
synchronized(obj)
{
      //保護的代碼段
     
}
這樣就實現了一個同步。
 
B.*同步方法的實現:
在方法的前面加上synchronized
Public synchronized void method1()
{
}
 
同步的方法可以完成跟同步塊一樣的結果。同步塊需要有一個對象,同步方法只需要
在方法的前面加上一個synchronized關鍵字。
 
3、同步塊的實現機制是怎樣完成的呢?java語言中每一個對象都有一個監視器,或者叫
做鎖。當我們第一個線程進來的時候首先要判斷一下obj這個對象的監視器是否被加
鎖,如果沒有被加鎖,那麼它就會將obj的監視器加鎖,然後往下執行(遇到sleep
睡眠)如果其它線程執行到Synchronized會判斷到obj已經被加鎖,那麼它們只能
等待。第一個線程執行完受保護的代碼後,會將obj的監視器解鎖。其它線程就可以
進入到這個同步的代碼段中。
 
同步方法的實現機制又是怎樣的呢?當我們一個線程進入這個方法的時候,也需要加
上一把鎖,同步的方法是給類中的this變量的監視器加鎖。當一個線程進入同步方法
的時候,會察看this對象是否加鎖,沒有加鎖則會加鎖。然後進入到方法內部,方法
執行完後,會將this解鎖,其它線程在this解鎖後纔可以進入。
4、同步方法利用的是this所代表的對象的鎖。
5、因爲靜態方法只屬於類本身,而不屬於某個對象。每個類都對應有一個class對象,
步靜態方法使用的就是方法所在的類所對應的class對象的監視器。
 
 
線程的死鎖
 
1、 哲學家進餐的問題。
2、 線程1鎖住了對象A的監視器,等待對象B的監視器,線程2鎖住了對象B的監視器,等待對象A的監視器,就造成了死鎖。
3、 Thread類中兩個廢棄的方法就很容易造成死鎖,suspend()、resume()
 
Wait、notify、nofityAll方法
 
1、 每一個對象除了有一個鎖之外,還有一個等待隊列(wait set),當一個對象剛創建的時候,它的等待隊列是空的。
2、我們應該在當前線程鎖住對象的鎖後,去調用該對象的wait方法。也就是說wait方法只能夠在同步方法或者同步塊中被調用。但我們調用wait方法的時候,這個線程就進入了這個對象的等待隊列當中。
3、 當調用對象的notify方法時,將從該對象的等待隊列中刪除一個任意選擇的線程,這個線程將再次成爲可運行的線程。
4、 當調用對象的notifyAll方法時,將從該對象的等待隊列中刪除所有等待的線程,這些線程將成爲可運行的線程。
5、 Wait和notify主要用於producer-consumer這種關係中。
6、 Wait和notify方法必須在一個同步的塊或者方法中被調用,並且針對同一對象的等待隊列
 
 
線程的狀態
 
1、 用new創建一個線程的時候,線程就處於new狀態。
當調用start方法的時候,線程處於可運行(Runnable)狀態。
一個尚未運行的線程是Runnable狀態,一個正在運行的線程也是Runnable狀態。
當一個正在運行的線程調用yield方法的時候,線程調度器會選擇另外一個線程去運行,調用yield方法的線程仍然處於Runnable狀態。
當我們調用sleep、wait、suspend或者I/O阻塞時線程就進入Not Runnable狀態。當一個線程sleep結束,或者調用了notify方法、resume方法或者I/O操作完成,線程就會從Not Runnable狀態返回Runnable狀態。
(resume和suspend這兩個函數已經被廢棄了,不建議我們再使用)
當一個處於可運行狀態的線程它的run方法執行完畢或者調用stop方法,線程就進入了Dead狀態終止了。
在Not Runnable狀態的時候,調用了stop方法,線程也會終止。
(stop方法是用來停止一個線程的,這個方法也已經廢棄了,因爲這個方法是不安全的,當調用stop方法時,它會解鎖先前鎖住的所有的監視器,這樣的話,其它的線程就可以去訪問被保護的對象,導致對象的狀態不一致,結果是不可預料的)
 
↓new thread
                 yield
     start                  sleep wait suspend I/O阻塞
New →→→→→→ Runnable →→→→→→→→→→→→→→→→→→Not Runnable
                          ←←←←←←←←←←←←←←←←←←       
                          Sleep結束、notify resume I/O操作完成     
                                                                  
                                                                 
                                                              
                               ↘ run方法退出               stop方法調用                            
                                 ↘ stop調用                
                                                        
                                                      
                                                    
                                                 
                                              Dead
 
線程的終止(兩種方式)
 
1、 設置一個flag變量
2、 interrupt()方法(可以中斷一個線程)
 
 
File
 
1、 一個File類的對象,表示了磁盤上的文件或目錄。
2、 File類提供了與平臺無關的方法來對磁盤上的文件或目錄進行操作。
3、 可以通過一個文件名來構造一個File類對象:
File(String pathname)
   也可以通過指定一個File對象,再指定它的文件名:
    File(String parent, String child)去構造一個File對象,這裏的parent表示
文件或目錄所在的父目錄。Child指定文件
名。
4、File類不只可以用來表示一個文件還可以表示一個目錄。
5、File類中:
    Boolean canRead() //檢測文件是否可讀
    Boolean canWrite()//檢測文件是否可寫
    Boolean createNewFile() 當我們構造一個File對象的時候可以調用
createNewFile()去創建一個新的文件
1、 在我們進行I/O操作的時候,經常會出現一些異常。如:磁盤空間不足、因爲磁盤故障不能創建文件。所以在用java.io包中的類進行編程的時候,經常會需要捕獲一些異常。爲了簡單起見,可以在main後throws出異常,讓java虛擬機去處理。
import java.io.*;
 
class FileTest
{
public static void main(String[] args) throws Exception
{
    File f = new File("1.txt");
    f.createNewFile();
}
}
2、 File類還可以用來創建目錄:
Boolean mkdir(); //用來創建一個目錄
例:
    File f = new File("1.txt");
     //f.createNewFile();
     f.mkdir();
3、 利用絕對路徑創建一個文件:
File f = new File("D://MyWorks//lessons//java//lesson7//1.txt");
f.createNewFile();
10、File類提供了常量表示目錄的分隔符:
    Static String separator //系統獨立的分隔符在windows下會被解釋爲“/”,
//在linux下爲/
    Static char separatorChar //也是用來表示分隔符,只不過一個使用String類
//型來表示,一個是用char表示
4、 在當前盤符下直接寫一個目錄分隔符,也可以表示當前盤符的根目錄。
 
 
 
I/O 流概述
 
  輸入/輸出處理是程序設計中非常重要的一部分,比如從鍵盤讀取數據、從文件中讀取數據或向文件中寫數據等等。
 
  Java把這些不同類型的輸入、輸出源抽象爲流(stream),用統一接口來表示,從而使程序簡單明瞭。
 
  Jdk 提供了包java.io,其中包括一系列的類來實現輸入/輸出處理。下面我們對java.io包的內容進行概要的介紹。
 
 I/O流的層次
 
  1.字節流
 
  2.字符流
 
  3.對象流
 
  4.其它
 
---------------------------------
 1.字節流:
 
  從InputStream和OutputStream派生出來的一系列類。這類流以字節(byte)爲基本處理單位。
  ◇ InputStream、OutputStream
  ◇ FileInputStream、FileOutputStream
  ◇ PipedInputStream、PipedOutputStream
  ◇ ByteArrayInputStream、ByteArrayOutputStream
  ◇ FilterInputStream、FilterOutputStream
  ◇ DataInputStream、DataOutputStream
  ◇ BufferedInputStream、BufferedOutputStream
 
 2.字符流:
 
  從Reader和Writer派生出的一系列類,這類流以16位的Unicode碼錶示的字符爲基本處理單位。
  ◇ Reader、Writer
  ◇ InputStreamReader、OutputStreamWriter
  ◇ FileReader、FileWriter
  ◇ CharArrayReader、CharArrayWriter
  ◇ PipedReader、PipedWriter
  ◇ FilterReader、FilterWriter
  ◇ BufferedReader、BufferedWriter
  ◇ StringReader、StringWriter
 
 3.對象流
 
  ◇ ObjectInputStream、ObjectOutputStream
 
 4.其它
 
  ◇ 文件處理:
  File、RandomAccessFile;
 
  ◇ 接口
  DataInput、DataOutput、ObjectInput、ObjectOutput;
 
-----------------------------------
1.InputStream
 
  ◇ 從流中讀取數據:
  int read( ); //讀取一個字節,返回值爲所讀的字節
  int read( byte b[ ] ); //讀取多個字節,放置到字節數組b中,通常
              //讀取的字節數量爲b的長度,返回值爲實際
              //讀取的字節的數量
  int read( byte b[ ], int off, int len ); //讀取len個字節,放置
                       //到以下標off開始字節
                       //數組b中,返回值爲實
                       //際讀取的字節的數量
  int available( );   //返回值爲流中尚未讀取的字節的數量
  long skip( long n ); //讀指針跳過n個字節不讀,返回值爲實際
             //跳過的字節數量
 
  ◇ 關閉流:
  close( ); //流操作完畢後必須關閉
  
  ◇ 使用輸入流中的標記:
  void mark( int readlimit ); //記錄當前讀指針所在位置,readlimit
                 //表示讀指針讀出readlimit個字節後
                //所標記的指針位置才失效
  void reset( );     //把讀指針重新指向用mark方法所記錄的位置
  boolean markSupported( ); //當前的流是否支持讀指針的記錄功能
 
  有關每個方法的使用,詳見java API。
 
 
 2.OutputStream
 
  ◇ 輸出數據:
  void write( int b );   //往流中寫一個字節b
  void write( byte b[ ] ); //往流中寫一個字節數組b
  void write( byte b[ ], int off, int len ); //把字節數組b中從
              //下標off開始,長度爲len的字節寫入流中
 
  ◇ flush( )       //刷空輸出流,並輸出所有被緩存的字節
  由於某些流支持緩存功能,該方法將把緩存中所有內容強制輸出到流中。
 
  ◇ 關閉流:
   close( );       //流操作完畢後必須關閉
 
-----------------------------------
進行I/O操作時可能會產生I/O例外,屬於非運行時例外,應該在程序中處理。如:FileNotFoundException, EOFException, IOException
-----------------------------------
文件的順序處理
 
  類FileInputStream和FileOutputStream用來進行文件I/O處理,由它們所提供的方法可以打開本地主機上的文件,並進行順序的讀/寫。例如,下列的語句段是順序讀取文件名爲text.txt的文件裏的內容,並顯示在控制檯上面,直到文件結束爲止。
 
例:
    FileInputStream fis;
   try{
    fis = new FileInputStream( "text.txt" );
   System.out.print( "content of text is : ");
     int b;
     while( (b=fis.read())!=-1 ) //順序讀取文件text裏的內容並賦值
                    給整型變量b,直到文件結束爲止。
     {              
       System.out.print( (char)b );
     }
   }catch( FileNotFoundException e ){
   System.out.println( e );
   }catch( IOException e ){
   System.out.println( e );
   }
 
----------------------------
過濾流
 
  過濾流在讀/寫數據的同時可以對數據進行處理,它提供了同步機制,使得某一時刻只有一個線程可以訪問一個I/O流,以防止多個線程同時對一個I/O流進行操作所帶來的意想不到的結果。類FilterInputStream和FilterOutputStream分別作爲所有過濾輸入流和輸出流的父類。
   過濾流類層次:
    java.lang.Object
    |
    +----java.io.InputStream
         |
         +----java.io.FilterInputStream
 
  爲了使用一個過濾流,必須首先把過濾流連接到某個輸入/出流上,通常通過在構造方法的參數中指定所要連接的輸入/出流來實現。例如:
 
  FilterInputStream( InputStream in );
  FilterOutputStream( OutputStream out );
 
幾種常見的過濾流:
 
    ◇ BufferedInputStream和BufferedOutputStream
    緩衝流,用於提高輸入/輸出處理的效率。
 
  ◇ DataInputStream 和 DataOutputStream
    不僅能讀/寫數據流,而且能讀/寫各種的java語言的基本類型,如:boolean,int       ,float等。
 
  ◇ LineNumberInputStream
    除了提供對輸入處理的支持外,LineNumberInputStream可以記錄當前的行號。
 
  ◇ PushbackInputStream
    提供了一個方法可以把剛讀過的字節退回到輸入流中,以便重新再讀一遍。
 
  ◇ PrintStream
    打印流的作用是把Java語言的內構類型以其字符表示形式送到相應的輸出流。
 
 
-------------------------------
字符流的處理
 
  java中提供了處理以16位的Unicode碼錶示的字符流的類,即以Reader和Writer 爲基類派生出的一系列類。
 
 4.7.1 Reader和Writer
 
  1.Reader類是處理所有字符流輸入類的父類。
 
  2. Writer類是處理所有字符流輸出類的父類。
 
這兩個類是抽象類,只是提供了一系列用於字符流處理的接口,不能生成這兩個類的實例,只能通過使用由它們派生出來的子類對象來處理字符流。
 
 1.Reader類是處理所有字符流輸入類的父類。
 
  ◇ 讀取字符
  public int read() throws IOException; //讀取一個字符,返回值爲讀取的字符
  public int read(char cbuf[]) throws IOException; /*讀取一系列字符到數組cbuf[]中,返回值爲實際讀取的字符的數量*/
  public abstract int read(char cbuf[],int off,int len) throws IOException;
  /*讀取len個字符,從數組cbuf[]的下標off處開始存放,返回值爲實際讀取的字符數量,該方法必須由子類實現*/
 
  ◇ 標記流
  public boolean markSupported(); //判斷當前流是否支持做標記
  public void mark(int readAheadLimit) throws IOException;
   //給當前流作標記,最多支持readAheadLimit個字符的回溯。
  public void reset() throws IOException; //將當前流重置到做標記處
 
  ◇ 關閉流
  public abstract void close() throws IOException;
 
 2. Writer類是處理所有字符流輸出類的父類。
 
  ◇ 向輸出流寫入字符
  public void write(int c) throws IOException;
  //將整型值c的低16位寫入輸出流
  public void write(char cbuf[]) throws IOException;
  //將字符數組cbuf[]寫入輸出流
  public abstract void write(char cbuf[],int off,int len) throws IOException;
  //將字符數組cbuf[]中的從索引爲off的位置處開始的len個字符寫入輸出流
  public void write(String str) throws IOException;
  //將字符串str中的字符寫入輸出流
  public void write(String str,int off,int len) throws IOException;
  //將字符串str 中從索引off開始處的len個字符寫入輸出流
 
  ◇ flush( )
  刷空輸出流,並輸出所有被緩存的字節。
 
  ◇ 關閉流
  public abstract void close() throws IOException;
 
-----------------------------------
3 BufferedReader和BufferedWriter
 
  生成流對象
 
  讀入/寫出字符
 
  下面是一個從鍵盤接收輸入數據的例子:
 
◇ 生成流對象
 
  public BufferedReader(Reader in); //使用缺省的緩衝區大小
  public BufferedReader(Reader in, int sz); //sz爲緩衝區的大小
  public BufferedWriter(Writer out);
  public BufferedWriter(Writer out, int sz);
 
 ◇ 讀入/寫出字符
 
  除了Reader和Writer中提供的基本的讀寫方法外,增加對整行字符的處理。
  public String readLine() throws IOException; //讀一行字符
  public void newLine() throws IOException; //寫一行字符
 
【例4-4】
 
  import java.io.*;
  public class NumberInput{
   public static void main(String args[]){
    try{
      InputStreamReader ir;
      BufferedReader in;
      ir=new InputStreamReader(System.in);
      //從鍵盤接收了一個字符串的輸入,並創建了一個字符輸入流的對象
      in=new BufferedReader(ir);
      String s=in.readLine();
      //從輸入流in中讀入一行,並將讀取的值賦值給字符串變量s
      System.out.println("Input value is: "+s);
      int i = Integer.parseInt(s);//轉換成int型
      i*=2;
      System.out.println("Input value changed after doubled: "+i);
    }catch(IOException e)
    {System.out.println(e);}
   }
  }
 
 
  注意:在讀取字符流時,如果不是來自於本地的,比如說來自於網絡上某處的與本地編碼方式不同的機器,那麼我們在構造輸入流時就不能簡單地使用本地缺省的編碼方式,否則讀出的字符就不正確;爲了正確地讀出異種機上的字符,我們應該使用下述方式構造輸入流對象:
  
    ir = new InputStreamReader(is, "8859_1");
 
  採用ISO 8859_1編碼方式,這是一種映射到ASCII碼的編碼方式,可以在不同平臺之間正確轉換字符。
 
 
 
網絡編程
 
 網絡基礎知識
 
  計算機網絡形式多樣,內容繁雜。網絡上的計算機要互相通信,必須遵循一定的協議。目前使用最廣泛的網絡協議是Internet上所使用的TCP/IP協議。
    網絡編程的目的就是指直接或間接地通過網絡協議與其他計算機進行通訊。網絡編程中有兩個主要的問題,一個是如何準確的定位網絡上一臺或多臺主機,另一個就是找到主機後如何可靠高效的進行數據傳輸。在TCP/IP協議中IP層主要負責網絡主機的定位,數據傳輸的路由,由IP地址可以唯一地確定Internet上的一臺主機。而TCP層則提供面向應用的可靠的或非可靠的數據傳輸機制,這是網絡編程的主要對象,一般不需要關心IP層是如何處理數據的。
 
  目前較爲流行的網絡編程模型是客戶機/服務器(C/S)結構。即通信雙方一方作爲服務器等待客戶提出請求並予以響應。客戶則在需要服務時向服務器提出申請。服務器一般作爲守護進程始終運行,監聽網絡端口,一旦有客戶請求,就會啓動一個服務進程來響應該客戶,同時自己繼續監聽服務端口,使後來的客戶也能及時得到服務。
 
    IP地址:標識計算機等網絡設備的網絡地址,由四個8位的二進制數組成,中間以小數點分隔。
    如:166.111.136.3 , 166.111.52.80
 
  主機名(hostname):網絡地址的助記名,按照域名進行分級管理。
    如:www.tsinghua.edu.cn
      www.fanso.com
  
  端口號(port number):網絡通信時同一機器上的不同進程的標識。
    如:80,21,23,25,其中1~1024爲系統保留的端口號
  
  服務類型(service):網絡的各種服務。
    http, telnet, ftp, smtp
 
  在Internet上IP地址和主機名是一一對應的,通過域名解析可以由主機名得到機器的IP,由於機器名更接近自然語言,容易記憶,所以使用比IP地址廣泛,但是對機器而言只有IP地址纔是有效的標識符。
 
  通常一臺主機上總是有很多個進程需要網絡資源進行網絡通訊。網絡通訊的對象準確的講不是主機,而應該是主機中運行的進程。這時候光有主機名或IP地址來標識這麼多個進程顯然是不夠的。端口號就是爲了在一臺主機上提供更多的網絡資源而採取得一種手段,也是TCP層提供的一種機制。只有通過主機名或IP地址和端口號的組合才能唯一的確定網絡通訊中的對象:進程。
 
服務類型是在TCP層上面的應用層的概念。基於TCP/IP協議可以構建出各種複雜的應用,服務類型是那些已經被標準化了的應用,一般都是網絡服務器(軟件)。讀者可以編寫自己的基於網絡的服務器,但都不能被稱作標準的服務類型。
 
    兩類傳輸協議:TCP;UDP
 
  儘管TCP/IP協議的名稱中只有TCP這個協議名,但是在TCP/IP的傳輸層同時存在TCP和UDP兩個協議。
 
    TCP是Tranfer Control Protocol的簡稱,是一種面向連接的保證可靠傳輸的協議。通過TCP協議傳輸,得到的是一個順序的無差錯的數據流。發送方和接收方的成對的兩個socket之間必須建立連接,以便在TCP協議的基礎上進行通信,當一個socket(通常都是server socket)等待建立連接時,另一個socket可以要求進行連接,一旦這兩個socket連接起來,它們就可以進行雙向數據傳輸,雙方都可以進行發送或接收操作。
 
  UDP是User Datagram Protocol的簡稱,是一種無連接的協議,每個數據報都是一個獨立的信息,包括完整的源地址和目的地址,它在網絡上以任何可能的路徑傳往目的地,因此能否到達目的地,到達目的地的時間以及內容的正確性都是不能被保證的。
 
  下面我們對這兩種協議做簡單比較:
 
  使用UDP時,每個數據報中都給出了完整的地址信息,因此無需要建立發送方和接收方的連接。對於TCP協議,由於它是一個面向連接的協議,在socket之間進行數據傳輸之前必然要建立連接,所以在TCP中多了一個連接建立的時間。
 
  使用UDP傳輸數據時是有大小限制的,每個被傳輸的數據報必須限定在64KB之內。而TCP沒有這方面的限制,一旦連接建立起來,雙方的socket就可以按統一的格式傳輸大量的數據。UDP是一個不可靠的協議,發送方所發送的數據報並不一定以相同的次序到達接收方。而TCP是一個可靠的協議,它確保接收方完全正確地獲取發送方所發送的全部數據。
 
  總之,TCP在網絡通信上有極強的生命力,例如遠程連接(Telnet)和文件傳輸(FTP)都需要不定長度的數據被可靠地傳輸。相比之下UDP操作簡單,而且僅需要較少的監護,因此通常用於局域網高可靠性的分散系統中client/server應用程序。
 
  既然有了保證可靠傳輸的TCP協議,爲什麼還要非可靠傳輸的UDP協議呢?主要的原因有兩個。一是可靠的傳輸是要付出代價的,對數據內容正確性的檢驗必然佔用計算機的處理時間和網絡的帶寬,因此TCP傳輸的效率不如UDP高。二是在許多應用中並不需要保證嚴格的傳輸可靠性,比如視頻會議系統,並不要求音頻視頻數據絕對的正確,只要保證連貫性就可以了,這種情況下顯然使用UDP會更合理一些。
 
-----------------------------
基於URL的高層次Java網絡編程
 
 一致資源定位器URL
 
    URL(Uniform Resource Locator)是一致資源定位器的簡稱,它表示Internet上某一資源的地址。通過URL我們可以訪問Internet上的各種網絡資源,比如最常見的WWW,FTP站點。瀏覽器通過解析給定的URL可以在網絡上查找相應的文件或其他資源。
 
  URL是最爲直觀的一種網絡定位方法。使用URL符合人們的語言習慣,容易記憶,所以應用十分廣泛。而且在目前使用最爲廣泛的TCP/IP中對於URL中主機名的解析也是協議的一個標準,即所謂的域名解析服務。使用URL進行網絡編程,不需要對協議本身有太多的瞭解,功能也比較弱,相對而言是比較簡單的.
 
URL的組成:
 
    protocol://resourceName
  協議名(protocol)指明獲取資源所使用的傳輸協議,如http、ftp、file等,資源名(resourceName)則應該是資源的完整地址,包括主機名、端口號、文件名或文件內部的一個引用。例如:
  http://www.sun.com/ 協議名://主機名
  http://home.netscape.com/home/welcome.html 協議名://機器名+文件名
  http://www.gamelan.com:80/Gamelan/network.html#BOTTOM 協議名://機器名+端口號+文件名+內部引用
 
創建一個URL:
 
    爲了表示URL, java.net中實現了類URL。我們可以通過下面的構造方法來初始化一個URL對象:
  (1) public URL (String spec);
     通過一個表示URL地址的字符串可以構造一個URL對象。
     URL urlBase=new URL("http://www. 263.net/")
 
  (2) public URL(URL context, String spec);
     通過基URL和相對URL構造一個URL對象。
     URL net263=new URL ("http://www.263.net/");
     URL index263=new URL(net263, "index.html")
 
  (3) public URL(String protocol, String host, String file);
     new URL("http", "www.gamelan.com", "/pages/Gamelan.net. html");
 
  (4) public URL(String protocol, String host, int port, String file);
     URL gamelan=new URL("http", "www.gamelan.com", 80, "Pages/Gamelan.network.html");
 
  注意:類URL的構造方法都聲明拋棄非運行時例外(MalformedURLException),因此生成URL對象時,我們必須要對這一例外進行處理,通常是用try-catch語句進行捕獲。格式如下:
 
 
  try{
     URL myURL= new URL(…)
  }catch (MalformedURLException e){
  …
  //exception handler code here
  …
  }
 
解析一個URL:
 
    一個URL對象生成後,其屬性是不能被改變的,但是我們可以通過類URL所提供的方法來獲取這些屬性:
   public String getProtocol() 獲取該URL的協議名。
   public String getHost() 獲取該URL的主機名。
   public int getPort() 獲取該URL的端口號,如果沒有設置端口,返回-1。
   public String getFile() 獲取該URL的文件名。
   public String getRef() 獲取該URL在文件中的相對位置。
   public String getQuery() 獲取該URL的查詢信息。
   public String getPath() 獲取該URL的路徑
   public String getAuthority() 獲取該URL的權限信息
   public String getUserInfo() 獲得使用者的信息
   public String getRef() 獲得該URL的錨
 
 
  下面的例子中,我們生成一個URL對象,並獲取它的各個屬性。
 
  import java.net.*;
  import java.io.*;
 
  public class ParseURL{
  public static void main (String [] args) throws Exception{
 
  URL Aurl=new URL("http://java.sun.com:80/docs/books/");
  URL tuto=new URL(Aurl,"tutorial.intro.html#DOWNLOADING");
  System.out.println("protocol="+ tuto.getProtocol());
  System.out.println("host ="+ tuto.getHost());
  System.out.println("filename="+ tuto.getFile());
  System.out.println("port="+ tuto.getPort());
  System.out.println("ref="+tuto.getRef());
  System.out.println("query="+tuto.getQuery());
  System.out.println("path="+tuto.getPath());
  System.out.println("UserInfo="+tuto.getUserInfo());
  System.out.println("Authority="+tuto.getAuthority());
  }
  }
 
  執行結果爲:
   protocol=http host =java.sun.com filename=/docs/books/tutorial.intro.html
   port=80
   ref=DOWNLOADING
   query=null
   path=/docs/books/tutorial.intro.html
   UserInfo=null
   Authority=java.sun.com:80
 
從URL讀取WWW網絡資源:
 
    當我們得到一個URL對象後,就可以通過它讀取指定的WWW資源。這時我們將使用URL的方法openStream(),其定義爲:
         InputStream openStream();
  
  方法openSteam()與指定的URL建立連接並返回InputStream類的對象以從這一連接中讀取數據。
例:
public class URLReader
{
  public static void main(String[] args) throws Exception
   {
                      //聲明拋出所有例外
    URL tirc = new URL("http://www.tirc1.cs.tsinghua.edu.cn/");
                      //構建一URL對象
    BufferedReader in = new BufferedReader(new InputStreamReader(tirc.openStream()));
    //使用openStream得到一輸入流並由此構造一個BufferedReader對象
    String inputLine;
    while ((inputLine = in.readLine()) != null)
                 //從輸入流不斷的讀數據,直到讀完爲止
       System.out.println(inputLine); //把讀入的數據打印到屏幕上
    in.close(); //關閉輸入流
  }
}
 
通過URLConnetction連接WWW:
 
    通過URL的方法openStream(),我們只能從網絡上讀取數據,如果我們同時還想輸出數據,例如向服務器端的CGI程序發送一些數據,我們必須先與URL建立連接,然後才能對其進行讀寫,這時就要用到類URLConnection了。CGI是公共網關接口(Common Gateway Interface)的簡稱,它是用戶瀏覽器和服務器端的應用程序進行連接的接口,有關CGI程序設計,請讀者參考有關書籍。
 
  類URLConnection也在包java.net中定義,它表示Java程序和URL在網絡上的通信連接。當與一個URL建立連接時,首先要在一個URL對象上通過方法openConnection()生成對應的URLConnection對象。例如下面的程序段首先生成一個指向地址http://edu.chinaren.com/index.shtml的對象,然後用openConnection()打開該URL對象上的一個連接,返回一個URLConnection對象。如果連接過程失敗,將產生IOException.
 
Try{
    URL netchinaren = new URL          ("http://edu.chinaren.com/index.shtml");
    URLConnectonn tc = netchinaren.openConnection();
  }catch(MalformedURLException e){ //創建URL()對象失敗
     
  }catch (IOException e){ //openConnection()失敗
     
  }
 
  類URLConnection提供了很多方法來設置或獲取連接參數,程序設計時最常使用的是getInputStream()和getOutputStream(),其定義爲:
     InputSteram getInputSteram();
     OutputSteram getOutputStream();
 
  通過返回的輸入/輸出流我們可以與遠程對象進行通信。看下面的例子:
  URL url =new URL ("http://www.javasoft.com/cgi-bin/backwards");
  //創建一URL對象
  URLConnectin con=url.openConnection();
  //由URL對象獲取URLConnection對象
  DataInputStream dis=new DataInputStream (con.getInputSteam());
  //由URLConnection獲取輸入流,並構造DataInputStream對象
  PrintStream ps=new PrintSteam(con.getOutupSteam());
  //由URLConnection獲取輸出流,並構造PrintStream對象
  String line=dis.readLine(); //從服務器讀入一行
  ps.println("client…"); //向服務器寫出字符串 "client…"
  
  其中backwards爲服務器端的CGI程序。實際上,類URL的方法openSteam()是通過URLConnection來實現的。它等價於
    openConnection().getInputStream();
  
  基於URL的網絡編程在底層其實還是基於下面要講的Socket接口的。WWW,FTP等標準化的網絡服務都是基於TCP協議的,所以本質上講URL編程也是基於TCP的一種應用。
   
 
--------------------------
基於Socket(套接字)的低層次Java網絡編程
 
 Socket通訊
 
    網絡上的兩個程序通過一個雙向的通訊連接實現數據的交換,這個雙向鏈路的一端稱爲一個Socket。Socket通常用來實現客戶方和服務方的連接。Socket是TCP/IP協議的一個十分流行的編程界面,一個Socket由一個IP地址和一個端口號唯一確定。
 
  在傳統的UNIX環境下可以操作TCP/IP協議的接口不止Socket一個,Socket所支持的協議種類也不光TCP/IP一種,因此兩者之間是沒有必然聯繫的。在Java環境下,Socket編程主要是指基於TCP/IP協議的網絡編程。
 
  說Socket編程是低層次網絡編程並不等於它功能不強大,恰恰相反,正因爲層次低,Socket編程比基於URL的網絡編程提供了更強大的功能和更靈活的控制,但是卻要更復雜一些。由於Java本身的特殊性,Socket編程在Java中可能已經是層次最低的網絡編程接口,在Java中要直接操作協議中更低的層次,需要使用Java的本地方法調用(JNI),在這裏就不予討論了。
 
-------------------
Socket通訊的一般過程
 
    前面已經提到Socket通常用來實現C/S結構。
 
  使用Socket進行Client/Server程序設計的一般連接過程是這樣的:Server端Listen(監聽)某個端口是否有連接請求,Client端向Server端發出Connect(連接)請求,Server端向Client端發回Accept(接受)消息。一個連接就建立起來了。Server端和Client端都可以通過Send,Write等方法與對方通信。
  
  對於一個功能齊全的Socket,都要包含以下基本結構,其工作過程包含以下四個基本的步驟:
  (1) 創建Socket;
  (2) 打開連接到Socket的輸入/出流;
  (3) 按照一定的協議對Socket進行讀/寫操作;
  (4) 關閉Socket.
 
  第三步是程序員用來調用Socket和實現程序功能的關鍵步驟,其他三步在各種程序中基本相同。
 
  以上4個步驟是針對TCP傳輸而言的,使用UDP進行傳輸時略有不同,在後面會有具體講解。
 
----------------
創建Socket:
 
    java在包java.net中提供了兩個類Socket和ServerSocket,分別用來表示雙向連接的客戶端和服務端。這是兩個封裝得非常好的類,使用很方便。其構造方法如下:
  Socket(InetAddress address, int port);
  Socket(InetAddress address, int port, boolean stream);
  Socket(String host, int prot);
  Socket(String host, int prot, boolean stream);
  Socket(SocketImpl impl)
  Socket(String host, int port, InetAddress localAddr, int localPort)
  Socket(InetAddress address, int port, InetAddress localAddr, int localPort)
  ServerSocket(int port);
  ServerSocket(int port, int backlog);
  ServerSocket(int port, int backlog, InetAddress bindAddr)
 
  其中address、host和port分別是雙向連接中另一方的IP地址、主機名和端口號,stream指明socket是流socket還是數據報socket,localPort表示本地主機的端口號,localAddr和bindAddr是本地機器的地址(ServerSocket的主機地址),impl是socket的父類,既可以用來創建serverSocket又可以用來創建Socket。count則表示服務端所能支持的最大連接數。例如:
  Socket client = new Socket("127.0.0.1", 80);
  ServerSocket server = new ServerSocket(80);
 
  注意,在選擇端口時,必須小心。每一個端口提供一種特定的服務,只有給出正確的端口,才能獲得相應的服務。0~1023的端口號爲系統所保留,例如http服務的端口號爲80,telnet服務的端口號爲21,ftp服務的端口號爲23, 所以我們在選擇端口號時,最好選擇一個大於1023的數以防止發生衝突。
 
  在創建socket時如果發生錯誤,將產生IOException,在程序中必須對之作出處理。所以在創建Socket或ServerSocket是必須捕獲或拋出例外。
 
----------------
客戶端的Socket
 
    下面是一個典型的創建客戶端Socket的過程。
   try{
     Socket socket=new Socket("127.0.0.1",4700);
     //127.0.0.1是TCP/IP協議中默認的本機地址
   }catch(IOException e){
     System.out.println("Error:"+e);
   }
-----------------
服務器端的ServerSocket:
 
    下面是一個典型的創建Server端ServerSocket的過程。
  ServerSocket server=null;
  try {
     server=new ServerSocket(4700);
     //創建一個ServerSocket在端口4700監聽客戶請求
  }catch(IOException e){
     System.out.println("can not listen to :"+e);
  }
  Socket socket=null;
  try {
    socket=server.accept();
    //accept()是一個阻塞的方法,一旦有客戶請求,它就會返回一個Socket對象用於同客戶進行交互
  }catch(IOException e){
    System.out.println("Error:"+e);
  }
 
  以上的程序是Server的典型工作模式,只不過在這裏Server只能接收一個請求,接受完後Server就退出了。實際的應用中總是讓它不停的循環接收,一旦有客戶請求,Server總是會創建一個服務線程來服務新來的客戶,而自己繼續監聽。程序中accept()是一個阻塞函數,所謂阻塞性方法就是說該方法被調用後,將等待客戶的請求,直到有一個客戶啓動並請求連接到相同的端口,然後accept()返回一個對應於客戶的socket。這時,客戶方和服務方都建立了用於通信的socket,接下來就是由各個socket分別打開各自的輸入/輸出流。
 
------------
打開輸入/出流
 
    類Socket提供了方法getInputStream ()和getOutStream()來得到對應的輸入/輸出流以進行讀/寫操作,這兩個方法分別返回InputStream和OutputSteam類對象。爲了便於讀/寫數據,我們可以在返回的輸入/輸出流對象上建立過濾流,如DataInputStream、DataOutputStream或PrintStream類對象,對於文本方式流對象,可以採用InputStreamReader和OutputStreamWriter、PrintWirter等處理。
 
  例如:
  PrintStream os=new PrintStream(new BufferedOutputStreem(socket.getOutputStream()));
  DataInputStream is=new DataInputStream(socket.getInputStream());
  PrintWriter out=new PrintWriter(socket.getOutStream(),true);
  BufferedReader in=new ButfferedReader(new InputSteramReader(Socket.getInputStream()));
 
----------------
關閉Socket
 
    每一個Socket存在時,都將佔用一定的資源,在Socket對象使用完畢時,要其關閉。關閉Socket可以調用Socket的Close()方法。在關閉Socket之前,應將與Socket相關的所有的輸入/輸出流全部關閉,以釋放所有的資源。而且要注意關閉的順序,與Socket相關的所有的輸入/輸出該首先關閉,然後再關閉Socket。
  os.close();
  is.close();
  socket.close();
 
  儘管Java有自動回收機制,網絡資源最終是會被釋放的。但是爲了有效的利用資源,建議讀者按照合理的順序主動釋放資源。
 
----------------
簡單的Client/Server程序設計
 
  下面我們給出一個用Socket實現的客戶和服務器交互的典型的C/S結構的演示程序,讀者通過仔細閱讀該程序,會對前面所討論的各個概念有更深刻的認識。程序的意義請參考註釋。
 
      1. 客戶端程序
 
  import java.io.*;
  import java.net.*;
  public class TalkClient {
    public static void main(String args[]) {
      try{
        Socket socket=new Socket("127.0.0.1",4700);
        //向本機的4700端口發出客戶請求
        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
        //由系統標準輸入設備構造BufferedReader對象
        PrintWriter os=new PrintWriter(socket.getOutputStream());
        //由Socket對象得到輸出流,並構造PrintWriter對象
        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
        //由Socket對象得到輸入流,並構造相應的BufferedReader對象
        String readline;
        readline=sin.readLine(); //從系統標準輸入讀入一字符串
        while(!readline.equals("bye")){
        //若從標準輸入讀入的字符串爲 "bye"則停止循環
          os.println(readline);
          //將從系統標準輸入讀入的字符串輸出到Server
          os.flush();
          //刷新輸出流,使Server馬上收到該字符串
          System.out.println("Client:"+readline);
          //在系統標準輸出上打印讀入的字符串
          System.out.println("Server:"+is.readLine());
          //從Server讀入一字符串,並打印到標準輸出上
          readline=sin.readLine(); //從系統標準輸入讀入一字符串
        } //繼續循環
        os.close(); //關閉Socket輸出流
        is.close(); //關閉Socket輸入流
        socket.close(); //關閉Socket
      }catch(Exception e) {
        System.out.println("Error"+e); //出錯,則打印出錯信息
      }
  }
}
 
 2. 服務器端程序
 
  import java.io.*;
  import java.net.*;
  import java.applet.Applet;
  public class TalkServer{
    public static void main(String args[]) {
      try{
        ServerSocket server=null;
        try{
          server=new ServerSocket(4700);
        //創建一個ServerSocket在端口4700監聽客戶請求
        }catch(Exception e) {
          System.out.println("can not listen to:"+e);
        //出錯,打印出錯信息
        }
 
        Socket socket=null;
        try{
          socket=server.accept();
          //使用accept()阻塞等待客戶請求,有客戶
          //請求到來則產生一個Socket對象,並繼續執行
        }catch(Exception e) {
          System.out.println("Error."+e);
          //出錯,打印出錯信息
        }
        String line;
        BufferedReader is=new BufferedReader(new InputStreamReader(socket.getInputStream()));
         //由Socket對象得到輸入流,並構造相應的BufferedReader對象
        PrintWriter os=newPrintWriter(socket.getOutputStream());
         //由Socket對象得到輸出流,並構造PrintWriter對象
        BufferedReader sin=new BufferedReader(new InputStreamReader(System.in));
         //由系統標準輸入設備構造BufferedReader對象
 
        System.out.println("Client:"+is.readLine());
        //在標準輸出上打印從客戶端讀入的字符串
        line=sin.readLine();
        //從標準輸入讀入一字符串
        while(!line.equals("bye")){
        //如果該字符串爲 "bye",則停止循環
          os.println(line);
          //向客戶端輸出該字符串
          os.flush();
          //刷新輸出流,使Client馬上收到該字符串
          System.out.println("Server:"+line);
          //在系統標準輸出上打印讀入的字符串
          System.out.println("Client:"+is.readLine());
          //從Client讀入一字符串,並打印到標準輸出上
          line=sin.readLine();
          //從系統標準輸入讀入一字符串
        }  //繼續循環
        os.close(); //關閉Socket輸出流
        is.close(); //關閉Socket輸入流
        socket.close(); //關閉Socket
        server.close(); //關閉ServerSocket
      }catch(Exception e){
        System.out.println("Error:"+e);
        //出錯,打印出錯信息
      }
    }
  }
 
--------------------
支持多客戶的client/server程序設計:
 
    前面提供的Client/Server程序只能實現Server和一個客戶的對話。在實際應用中,往往是在服務器上運行一個永久的程序,它可以接收來自其他多個客戶端的請求,提供相應的服務。爲了實現在服務器方給多個客戶提供服務的功能,需要對上面的程序進行改造,利用多線程實現多客戶機制。服務器總是在指定的端口上監聽是否有客戶請求,一旦監聽到客戶請求,服務器就會啓動一個專門的服務線程來響應該客戶的請求,而服務器本身在啓動完線程之後馬上又進入監聽狀態,等待下一個客戶的到來。
    例:ThreadedEchoServer
 
 
-----------------
什麼是Datagram:
 
     所謂數據報(Datagram)就跟日常生活中的郵件系統一樣,是不能保證可靠的寄到的,而面向鏈接的TCP就好比電話,雙方能肯定對方接受到了信息。在本章前面,我們已經對UDP和TCP進行了比較,在這裏再稍作小節:
 
  TCP,可靠,傳輸大小無限制,但是需要連接建立時間,差錯控制開銷大。
  UDP,不可靠,差錯控制開銷較小,傳輸大小限制在64K以下,不需要建立連接。
 
  總之,這兩種協議各有特點,應用的場合也不同,是完全互補的兩個協議,在TCP/IP協議中佔有同樣重要的地位,要學好網絡編程,兩者缺一不可。
 
-------------------
Datagram通訊的表示方法:
DatagramSocket
DatagramPacket
 
    包java.net中提供了兩個類DatagramSocket和DatagramPacket用來支持數據報通信,DatagramSocket用於在程序之間建立傳送數據報的通信連接, DatagramPacket則用來表示一個數據報。先來看一下DatagramSocket的構造方法:
   DatagramSocket();
   DatagramSocket(int prot);
   DatagramSocket(int port, InetAddress laddr)
  
  其中,port指明socket所使用的端口號,如果未指明端口號,則把socket連接到本地主機上一個可用的端口。laddr指明一個可用的本地地址。給出端口號時要保證不發生端口衝突,否則會生成SocketException類例外。注意:上述的兩個構造方法都聲明拋棄非運行時例外SocketException,程序中必須進行處理,或者捕獲、或者聲明拋棄。
 
用數據報方式編寫client/server程序時,無論在客戶方還是服務方,首先都要建立一個DatagramSocket對象,用來接收或發送數據報,然後使用DatagramPacket類對象作爲傳輸數據的載體。下面看一下DatagramPacket的構造方法 :
   DatagramPacket(byte buf[],int length);
   DatagramPacket(byte buf[], int length, InetAddress addr, int port);
   DatagramPacket(byte[] buf, int offset, int length);
   DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port);
 
  其中,buf中存放數據報數據,length爲數據報中數據的長度,addr和port旨明目的地址,offset指明瞭數據報的位移量。
 
  在接收數據前,應該採用上面的第一種方法生成一個DatagramPacket對象,給出接收數據的緩衝區及其長度。然後調用DatagramSocket 的方法receive()等待數據報的到來,receive()將一直等待,直到收到一個數據報爲止。
  DatagramPacket packet=new DatagramPacket(buf, 256);
  Socket.receive (packet);
 
  發送數據前,也要先生成一個新的DatagramPacket對象,這時要使用上面的第二種構造方法,在給出存放發送數據的緩衝區的同時,還要給出完整的目的地址,包括IP地址和端口號。發送數據是通過DatagramSocket的方法send()實現的,send()根據數據報的目的地址來尋徑,以傳遞數據報。
  DatagramPacket packet=new DatagramPacket(buf, length, address, port);
  Socket.send(packet);
 
在構造數據報時,要給出InetAddress類參數。類InetAddress在包java.net中定義,用來表示一個Internet地址,我們可以通過它提供的類方法getByName()從一個表示主機名的字符串獲取該主機的IP地址,然後再獲取相應的地址信息。
 
-------------
基於UDP的簡單的Client/Server程序設計:
 
 
    可以看出使用UDP和使用TCP在程序上還是有很大的區別的。一個比較明顯的區別是,UDP的Socket編程是不提供監聽功能的,也就是說通信雙方更爲平等,面對的接口是完全一樣的。但是爲了用UDP實現C/S結構,在使用UDP時可以使用DatagramSocket.receive()來實現類似於監聽的功能。因爲receive()是阻塞的函數,當它返回時,緩衝區裏已經填滿了接受到的一個數據報,並且可以從該數據報得到發送方的各種信息,這一點跟accept()是很相象的,因而可以根據讀入的數據報來決定下一步的動作,這就達到了跟網絡監聽相似的效果。
                                                                         Email:[email protected]
 
 
 
 
 
  
 
 
 
  
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章