<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Energy of Love  
    日歷
    <2011年12月>
    27282930123
    45678910
    11121314151617
    18192021222324
    25262728293031
    1234567
    統(tǒng)計(jì)
    • 隨筆 - 70
    • 文章 - 0
    • 評(píng)論 - 80
    • 引用 - 0

    導(dǎo)航

    常用鏈接

    留言簿

    隨筆分類(lèi)

    隨筆檔案

    搜索

    •  

    最新評(píng)論

    閱讀排行榜

    評(píng)論排行榜

     

    2011年12月6日

    11.3 I/O類(lèi)使用
             由于在IO操作中,需要使用的數(shù)據(jù)源有很多,作為一個(gè)IO技術(shù)的初學(xué)者,從讀寫(xiě)文件開(kāi)始學(xué)習(xí)IO技術(shù)是一個(gè)比較好的選擇。因?yàn)槲募且环N常見(jiàn)的數(shù)據(jù)源,而且讀寫(xiě)文件也是程序員進(jìn)行IO編程的一個(gè)基本能力。本章IO類(lèi)的使用就從讀寫(xiě)文件開(kāi)始。
    11.3.1 文件操作
             文件(File)是 最常見(jiàn)的數(shù)據(jù)源之一,在程序中經(jīng)常需要將數(shù)據(jù)存儲(chǔ)到文件中,例如圖片文件、聲音文件等數(shù)據(jù)文件,也經(jīng)常需要根據(jù)需要從指定的文件中進(jìn)行數(shù)據(jù)的讀取。當(dāng)然, 在實(shí)際使用時(shí),文件都包含一個(gè)的格式,這個(gè)格式需要程序員根據(jù)需要進(jìn)行設(shè)計(jì),讀取已有的文件時(shí)也需要熟悉對(duì)應(yīng)的文件格式,才能把數(shù)據(jù)從文件中正確的讀取出 來(lái)。
             文件的存儲(chǔ)介質(zhì)有很多,例如硬盤(pán)、光盤(pán)和U盤(pán)等,由于IO類(lèi)設(shè)計(jì)時(shí),從數(shù)據(jù)源轉(zhuǎn)換為流對(duì)象的操作由API實(shí)現(xiàn)了,所以存儲(chǔ)介質(zhì)的不同對(duì)于程序員來(lái)說(shuō)是透明的,和實(shí)際編寫(xiě)代碼無(wú)關(guān)。
    11.3.1.1 文件的概念
             文件是計(jì)算機(jī)中一種基本的數(shù)據(jù)存儲(chǔ)形式,在實(shí)際存儲(chǔ)數(shù)據(jù)時(shí),如果對(duì)于數(shù)據(jù)的讀寫(xiě)速度要求不是很高,存儲(chǔ)的數(shù)據(jù)量不是很大時(shí),使用文件作為一種持久數(shù)據(jù)存儲(chǔ)的方式是比較好的選擇。
             存儲(chǔ)在文件內(nèi)部的數(shù)據(jù)和內(nèi)存中的數(shù)據(jù)不同,存儲(chǔ)在文件中的數(shù)據(jù)是一種“持久存儲(chǔ)”,也就是當(dāng)程序退出或計(jì)算機(jī)關(guān)機(jī)以后,數(shù)據(jù)還是存在的,而內(nèi)存內(nèi)部的數(shù)據(jù)在程序退出或計(jì)算機(jī)關(guān)機(jī)以后,數(shù)據(jù)就丟失了。
             在不同的存儲(chǔ)介質(zhì)中,文件中的數(shù)據(jù)都是以一定的順序依次存儲(chǔ)起來(lái),在實(shí)際讀取時(shí)由硬件以及操作系統(tǒng)完成對(duì)于數(shù)據(jù)的控制,保證程序讀取到的數(shù)據(jù)和存儲(chǔ)的順序保持一致。
             每個(gè)文件以一個(gè)文件路徑和文件名稱(chēng)進(jìn)行表示,在需要訪問(wèn)該文件的時(shí),只需要知道該文件的路徑以及文件的全名即可。在不同的操作系統(tǒng)環(huán)境下,文件路徑的表示形式是不一樣的,例如在Windows操作系統(tǒng)中一般的表示形式為C:\windows\system,而Unix上的表示形式為/user/my。所以如果需要讓Java程序能夠在不同的操作系統(tǒng)下運(yùn)行,書(shū)寫(xiě)文件路徑時(shí)還需要比較注意。
    11.3.1.1.1 絕對(duì)路徑和相對(duì)路徑
             絕對(duì)路徑是指書(shū)寫(xiě)文件的完整路徑,例如d:\java\Hello.java,該路徑中包含文件的完整路徑d:\java以及文件的全名Hello.java。使用該路徑可以唯一的找到一個(gè)文件,不會(huì)產(chǎn)生歧義。但是使用絕對(duì)路徑在表示文件時(shí),受到的限制很大,且不能在不同的操作系統(tǒng)下運(yùn)行,因?yàn)椴煌僮飨到y(tǒng)下絕對(duì)路徑的表達(dá)形式存在不同。
             相對(duì)路徑是指書(shū)寫(xiě)文件的部分路徑,例如\test\Hello.java,該路徑中只包含文件的部分路徑\test和文件的全名Hello.java,部分路徑是指當(dāng)前路徑下的子路徑,例如當(dāng)前程序在d:\abc下運(yùn)行,則該文件的完整路徑就是d:\abc\test。使用這種形式,可以更加通用的代表文件的位置,使得文件路徑產(chǎn)生一定的靈活性。
             在Eclipse項(xiàng)目中運(yùn)行程序時(shí),當(dāng)前路徑是項(xiàng)目的根目錄,例如工作空間存儲(chǔ)在d:\javaproject,當(dāng)前項(xiàng)目名稱(chēng)是Test,則當(dāng)前路徑是:d:\javaproject\Test。在控制臺(tái)下面運(yùn)行程序時(shí),當(dāng)前路徑是class文件所在的目錄,如果class文件包含包名,則以該class文件最頂層的包名作為當(dāng)前路徑。
             另外在Java語(yǔ)言的代碼內(nèi)部書(shū)寫(xiě)文件路徑時(shí),需要注意大小寫(xiě),大小寫(xiě)需要保持一致,路徑中的文件夾名稱(chēng)區(qū)分大小寫(xiě)。由于’\’是Java語(yǔ)言中的特殊字符,所以在代碼內(nèi)部書(shū)寫(xiě)文件路徑時(shí),例如代表“c:\test\java\Hello.java”時(shí),需要書(shū)寫(xiě)成“c:\\test\\java\\Hello.java”或“c:/test/java/Hello.java”,這些都需要在代碼中注意。
    11.3.1.1.2 文件名稱(chēng)
             文件名稱(chēng)一般采用“文件名.后綴名”的形式進(jìn)行命名,其中“文件名”用來(lái)表示文件的作用,而使用后綴名來(lái)表示文件的類(lèi)型,這是當(dāng)前操作系統(tǒng)中常見(jiàn)的一種形式,例如“readme.txt”文件,其中readme代表該文件時(shí)說(shuō)明文件,而txt后綴名代表文件時(shí)文本文件類(lèi)型,在操作系統(tǒng)中,還會(huì)自動(dòng)將特定格式的后綴名和對(duì)應(yīng)的程序關(guān)聯(lián),在雙擊該文件時(shí)使用特定的程序打開(kāi)。
             其實(shí)在文件名稱(chēng)只是一個(gè)標(biāo)示,和實(shí)際存儲(chǔ)的文件內(nèi)容沒(méi)有必然的聯(lián)系,只是使用這種方式方便文件的使用。在程序中需要存儲(chǔ)數(shù)據(jù)時(shí),如果自己設(shè)計(jì)了特定的文件格式,則可以自定義文件的后綴名,來(lái)標(biāo)示自己的文件類(lèi)型。
             和文件路徑一樣,在Java代碼內(nèi)部書(shū)寫(xiě)文件名稱(chēng)時(shí)也區(qū)分大小寫(xiě),文件名稱(chēng)的大小寫(xiě)必須和操作系統(tǒng)中的大小寫(xiě)保持一致。
             另外,在書(shū)寫(xiě)文件名稱(chēng)時(shí)不要忘記書(shū)寫(xiě)文件的后綴名。
    11.3.1.2 File類(lèi)
             為了很方便的代表文件的概念,以及存儲(chǔ)一些對(duì)于文件的基本操作,在java.io包中設(shè)計(jì)了一個(gè)專(zhuān)門(mén)的類(lèi)——File類(lèi)。
             在File類(lèi)中包含了大部分和文件操作的功能方法,該類(lèi)的對(duì)象可以代表一個(gè)具體的文件或文件夾,所以以前曾有人建議將該類(lèi)的類(lèi)名修改成FilePath,因?yàn)樵擃?lèi)也可以代表一個(gè)文件夾,更準(zhǔn)確的說(shuō)是可以代表一個(gè)文件路徑。
             下面介紹一下File類(lèi)的基本使用。
             1、File對(duì)象代表文件路徑
    File類(lèi)的對(duì)象可以代表一個(gè)具體的文件路徑,在實(shí)際代表時(shí),可以使用絕對(duì)路徑也可以使用相對(duì)路徑。
    下面是創(chuàng)建的文件對(duì)象示例。
                       public File(String pathname)
             該示例中使用一個(gè)文件路徑表示一個(gè)File類(lèi)的對(duì)象,例如:
                       File f1 = new File(“d:\\test\\1.txt”);
                       File f2 = new File(“1.txt”);
                      File f3 = new File(“e:\\abc”);
    這里的f1和f2對(duì)象分別代表一個(gè)文件,f1是絕對(duì)路徑,而f2是相對(duì)路徑,f3則代表一個(gè)文件夾,文件夾也是文件路徑的一種。
    public File(String parent, String child)
                                也可以使用父路徑和子路徑結(jié)合,實(shí)現(xiàn)代表文件路徑,例如:
                                         File f4 = new File(“d:\\test\\”,”1.txt”);
                                這樣代表的文件路徑是:d:\test\1.txt。
             2、File類(lèi)常用方法
    File類(lèi)中包含了很多獲得文件或文件夾屬性的方法,使用起來(lái)比較方便,下面將常見(jiàn)的方法介紹如下:
                       a、createNewFile方法
                                         public boolean createNewFile() throws IOException
    該方法的作用是創(chuàng)建指定的文件。該方法只能用于創(chuàng)建文件,不能用于創(chuàng)建文件夾,且文件路徑中包含的文件夾必須存在。
                       b、delect方法
                                         public boolean delete()
    該方法的作用是刪除當(dāng)前文件或文件夾。如果刪除的是文件夾,則該文件夾必須為空。如果需要?jiǎng)h除一個(gè)非空的文件夾,則需要首先刪除該文件夾內(nèi)部的每個(gè)文件和文件夾,然后在可以刪除,這個(gè)需要書(shū)寫(xiě)一定的邏輯代碼實(shí)現(xiàn)。
                       c、exists方法
                                         public boolean exists()
                                該方法的作用是判斷當(dāng)前文件或文件夾是否存在。
                       d、getAbsolutePath方法
                                         public String getAbsolutePath()
    該方法的作用是獲得當(dāng)前文件或文件夾的絕對(duì)路徑。例如c:\test\1.t則返回c:\test\1.t。
                       e、getName方法
                                         public String getName()
                                該方法的作用是獲得當(dāng)前文件或文件夾的名稱(chēng)。例如c:\test\1.t,則返回1.t。
                       f、getParent方法
                                         public String getParent()
                                該方法的作用是獲得當(dāng)前路徑中的父路徑。例如c:\test\1.t則返回c:\test。
                       g、isDirectory方法
                                         public boolean isDirectory()
                                該方法的作用是判斷當(dāng)前File對(duì)象是否是目錄。
                       h、isFile方法
                                         public boolean isFile()
                                該方法的作用是判斷當(dāng)前File對(duì)象是否是文件。
                       i、length方法
                                         public long length()
    該方法的作用是返回文件存儲(chǔ)時(shí)占用的字節(jié)數(shù)。該數(shù)值獲得的是文件的實(shí)際大小,而不是文件在存儲(chǔ)時(shí)占用的空間數(shù)。
                       j、list方法
                                         public String[] list()
    該方法的作用是返回當(dāng)前文件夾下所有的文件名和文件夾名稱(chēng)。說(shuō)明,該名稱(chēng)不是絕對(duì)路徑。
                       k、listFiles方法
                                         public File[] listFiles()
                                該方法的作用是返回當(dāng)前文件夾下所有的文件對(duì)象。
                       l、mkdir方法
                                         public boolean mkdir()
    該方法的作用是創(chuàng)建當(dāng)前文件文件夾,而不創(chuàng)建該路徑中的其它文件夾。假設(shè)d盤(pán)下只有一個(gè)test文件夾,則創(chuàng)建d:\test\abc文件夾則成功,如果創(chuàng)建d:\a\b文件夾則創(chuàng)建失敗,因?yàn)樵撀窂街衐:\a文件夾不存在。如果創(chuàng)建成功則返回true,否則返回false。
                       m、mkdirs方法
                                         public boolean mkdirs()
    該方法的作用是創(chuàng)建文件夾,如果當(dāng)前路徑中包含的父目錄不存在時(shí),也會(huì)自動(dòng)根據(jù)需要?jiǎng)?chuàng)建。
                       n、renameTo方法
                                         public boolean renameTo(File dest)
    該方法的作用是修改文件名。在修改文件名時(shí)不能改變文件路徑,如果該路徑下已有該文件,則會(huì)修改失敗。
                       o、setReadOnly方法
                                         public boolean setReadOnly()
                                該方法的作用是設(shè)置當(dāng)前文件或文件夾為只讀。
             3、File類(lèi)基本示例
                       以上各方法實(shí)現(xiàn)的測(cè)試代碼如下:
                                import java.io.File;
    /**
     * File類(lèi)使用示例
     */
    public class FileDemo {
             public static void main(String[] args) {
                       //創(chuàng)建File對(duì)象
                       File f1 = new File("d:\\test");
                       File f2 = new File("1.txt");
                       File f3 = new File("e:\\file.txt");
                       File f4 = new File("d:\\","1.txt");
                       //創(chuàng)建文件
                       try{
                                boolean b = f3.createNewFile();
                       }catch(Exception e){
                                e.printStackTrace();
                       }
                       //判斷文件是否存在
                       System.out.println(f4.exists());
                       //獲得文件的絕對(duì)路徑
                       System.out.println(f3.getAbsolutePath());
                       //獲得文件名
                       System.out.println(f3.getName());
                       //獲得父路徑
                       System.out.println(f3.getParent());
                       //判斷是否是目錄
                       System.out.println(f1.isDirectory());
                       //判斷是否是文件
                       System.out.println(f3.isFile());
                       //獲得文件長(zhǎng)度
                       System.out.println(f3.length());
                       //獲得當(dāng)前文件夾下所有文件和文件夾名稱(chēng)
                       String[] s = f1.list();
                       for(int i = 0;i < s.length;i++){
                                System.out.println(s[i]);
                       }
                       //獲得文件對(duì)象
                       File[] f5 = f1.listFiles();
                       for(int i = 0;i < f5.length;i++){
                                System.out.println(f5[i]);
                       }
                       //創(chuàng)建文件夾
                       File f6 = new File("e:\\test\\abc");
                       boolean b1 = f6.mkdir();
                       System.out.println(b1);
                       b1 = f6.mkdirs();
                       System.out.println(b1);
                       //修改文件名
                       File f7 = new File("e:\\a.txt");
                       boolean b2 = f3.renameTo(f7);
                       System.out.println(b2);
                       //設(shè)置文件為只讀
                       f7.setReadOnly();             
             }
    }
             4、File類(lèi)綜合示例
    下面以?xún)蓚€(gè)示例演示File類(lèi)的綜合使用。第一個(gè)示例是顯示某個(gè)文件夾下的所有文件和文件夾,原理是輸出當(dāng)前名稱(chēng),然后判斷當(dāng)前File對(duì) 象是文件還是文件夾,如果則獲得該文件夾下的所有子文件和子文件夾,并遞歸調(diào)用該方法實(shí)現(xiàn)。第二個(gè)示例是刪除某個(gè)文件夾下的所有文件和文件夾,原理是判斷 是否是文件,如果是文件則直接刪除,如果是文件夾,則獲得該文件夾下所有的子文件和子文件夾,然后遞歸調(diào)用該方法處理所有子文件和子文件夾,然后將空文件 夾刪除。則測(cè)試時(shí)謹(jǐn)慎使用第二個(gè)方法,以免刪除自己有用的數(shù)據(jù)文件。示例代碼如下:
                                import java.io.File;
    /**
     * 文件綜合使用示例
     */
    public class AdvanceFileDemo {
             public static void main(String[] args) {
                       File f = new File("e:\\Book");
                       printAllFile(f);
                       File f1 = new File("e:\\test");
                       deleteAll(f1);
             }
            
             /**
              * 打印f路徑下所有的文件和文件夾
              * @param f 文件對(duì)象
              */
             public static void printAllFile(File f){
                       //打印當(dāng)前文件名
                       System.out.println(f.getName());
                       //是否是文件夾
                       if(f.isDirectory()){
                                //獲得該文件夾下所有子文件和子文件夾
                                File[] f1 = f.listFiles();
                                //循環(huán)處理每個(gè)對(duì)象
                                int len = f1.length;
                                for(int i = 0;i < len;i++){
                                         //遞歸調(diào)用,處理每個(gè)文件對(duì)象
                                         printAllFile(f1[i]);
                                }
                       }
             }
            
             /**
              * 刪除對(duì)象f下的所有文件和文件夾
              * @param f 文件路徑
              */
             public static void deleteAll(File f){
                       //文件
                       if(f.isFile()){
                                f.delete();
                       }else{ //文件夾
                                //獲得當(dāng)前文件夾下的所有子文件和子文件夾
                                File f1[] = f.listFiles();
                                //循環(huán)處理每個(gè)對(duì)象
                                int len = f1.length;
                                for(int i = 0;i < len;i++){
                                         //遞歸調(diào)用,處理每個(gè)文件對(duì)象
                                         deleteAll(f1[i]);
                                }
                                //刪除當(dāng)前文件夾
                                f.delete();
                       }
             }
    }
             關(guān)于File類(lèi)的使用就介紹這么多,其它的方法和使用時(shí)需要注意的問(wèn)題還需要多進(jìn)行練習(xí)和實(shí)際使用。
    11.3.1.3 讀取文件
             雖然前面介紹了流的概念,但是這個(gè)概念對(duì)于初學(xué)者來(lái)說(shuō),還是比較抽象的,下面以實(shí)際的讀取文件為例子,介紹流的概念,以及輸入流的基本使用。
             按照前面介紹的知識(shí),將文件中的數(shù)據(jù)讀入程序,是將程序外部的數(shù)據(jù)傳入程序中,應(yīng)該使用輸入流——InputStream或Reader。而由于讀取的是特定的數(shù)據(jù)源——文件,則可以使用輸入對(duì)應(yīng)的子類(lèi)FileInputStream或FileReader實(shí)現(xiàn)。
             在實(shí)際書(shū)寫(xiě)代碼時(shí),需要首先熟悉讀取文件在程序中實(shí)現(xiàn)的過(guò)程。在Java語(yǔ)言的IO編程中,讀取文件是分兩個(gè)步驟:1、將文件中的數(shù)據(jù)轉(zhuǎn)換為流,2、讀取流內(nèi)部的數(shù)據(jù)。其中第一個(gè)步驟由系統(tǒng)完成,只需要?jiǎng)?chuàng)建對(duì)應(yīng)的流對(duì)象即可,對(duì)象創(chuàng)建完成以后步驟1就完成了,第二個(gè)步驟使用輸入流對(duì)象中的read方法即可實(shí)現(xiàn)了。
             使用輸入流進(jìn)行編程時(shí),代碼一般分為3個(gè)部分:1、創(chuàng)建流對(duì)象,2、讀取流對(duì)象內(nèi)部的數(shù)據(jù),3、關(guān)閉流對(duì)象。下面以讀取文件的代碼示例:
                       import java.io.*;
    /**
     * 使用FileInputStream讀取文件
     */
    public class ReadFile1 {
             public static void main(String[] args) {
                       //聲明流對(duì)象
                       FileInputStream fis = null;                 
                       try{
                                //創(chuàng)建流對(duì)象
                                fis = new FileInputStream("e:\\a.txt");
                                //讀取數(shù)據(jù),并將讀取到的數(shù)據(jù)存儲(chǔ)到數(shù)組中
                                byte[] data = new byte[1024]; //數(shù)據(jù)存儲(chǔ)的數(shù)組
                                int i = 0; //當(dāng)前下標(biāo)
                                //讀取流中的第一個(gè)字節(jié)數(shù)據(jù)
                                int n = fis.read();
                                //依次讀取后續(xù)的數(shù)據(jù)
                                while(n != -1){ //未到達(dá)流的末尾
                                         //將有效數(shù)據(jù)存儲(chǔ)到數(shù)組中
                                         data[i] = (byte)n;
                                         //下標(biāo)增加
                                         i++;
                                         //讀取下一個(gè)字節(jié)的數(shù)據(jù)
                                          n = fis.read();
                                }
                               
                                //解析數(shù)據(jù)
                                String s = new String(data,0,i);
                                //輸出字符串
                                System.out.println(s);
                       }catch(Exception e){
                                e.printStackTrace();
                       }finally{
                                try{
                                         //關(guān)閉流,釋放資源
                                         fis.close();
                                }catch(Exception e){}
                       }
             }
    }
             在該示例代碼中,首先創(chuàng)建一個(gè)FileInputStream類(lèi)型的對(duì)象fis:
                       fis = new FileInputStream("e:\\a.txt");
             這樣建立了一個(gè)連接到數(shù)據(jù)源e:\a.txt的流,并將該數(shù)據(jù)源中的數(shù)據(jù)轉(zhuǎn)換為流對(duì)象fis,以后程序讀取數(shù)據(jù)源中的數(shù)據(jù),只需要從流對(duì)象fis中讀取即可。
             讀取流fis中的數(shù)據(jù),需要使用read方法,該方法是從InputStream類(lèi)中繼承過(guò)來(lái)的方法,該方法的作用是每次讀取流中的一個(gè)字節(jié),如果需要讀取流中的所有數(shù)據(jù),需要使用循環(huán)讀取,當(dāng)?shù)竭_(dá)流的末尾時(shí),read方法的返回值是-1。
             在該示例中,首先讀取流中的第一個(gè)字節(jié):
                       int n = fis.read();
             并將讀取的值賦值給int值n,如果流fis為空,則n的值是-1,否則n中的最后一個(gè)字節(jié)包含的時(shí)流fis中的第一個(gè)字節(jié),該字節(jié)被讀取以后,將被從流fis中刪除。
             然后循環(huán)讀取流中的其它數(shù)據(jù),如果讀取到的數(shù)據(jù)不是-1,則將已經(jīng)讀取到的數(shù)據(jù)n強(qiáng)制轉(zhuǎn)換為byte,即取n中的有效數(shù)據(jù)——最后一個(gè)字節(jié),并存儲(chǔ)到數(shù)組data中,然后調(diào)用流對(duì)象fis中的read方法繼續(xù)讀取流中的下一個(gè)字節(jié)的數(shù)據(jù)。一直這樣循環(huán)下去,直到讀取到的數(shù)據(jù)是-1,也就是讀取到流的末尾則循環(huán)結(jié)束。
             這里的數(shù)組長(zhǎng)度是1024,所以要求流中的數(shù)據(jù)長(zhǎng)度不能超過(guò)1024,所以該示例代碼在這里具有一定的局限性。如果流的數(shù)據(jù)個(gè)數(shù)比較多,則可以將1024擴(kuò)大到合適的個(gè)數(shù)即可。
             經(jīng)過(guò)上面的循環(huán)以后,就可以將流中的數(shù)據(jù)依次存儲(chǔ)到data數(shù)組中,存儲(chǔ)到data數(shù)組中有效數(shù)據(jù)的個(gè)數(shù)是i個(gè),即循環(huán)次數(shù)。
             其實(shí)截至到這里,IO操作中的讀取數(shù)據(jù)已經(jīng)完成,然后再按照數(shù)據(jù)源中的數(shù)據(jù)格式,這里是文件的格式,解析讀取出的byte數(shù)組即可。
             該示例代碼中的解析,只是將從流對(duì)象中讀取到的有效的數(shù)據(jù),也就是data數(shù)組中的前n個(gè)數(shù)據(jù),轉(zhuǎn)換為字符串,然后進(jìn)行輸出。
             在該示例代碼中,只是在catch語(yǔ)句中輸出異常的信息,便于代碼的調(diào)試,在實(shí)際的程序中,需要根據(jù)情況進(jìn)行一定的邏輯處理,例如給出提示信息等。
             最后在finally語(yǔ)句塊中,關(guān)閉流對(duì)象fis,釋放流對(duì)象占用的資源,關(guān)閉數(shù)據(jù)源,實(shí)現(xiàn)流操作的結(jié)束工作。
             上面詳細(xì)介紹了讀取文件的過(guò)程,其實(shí)在實(shí)際讀取流數(shù)據(jù)時(shí),還可以使用其它的read方法,下面的示例代碼是使用另外一個(gè)read方法實(shí)現(xiàn)讀取的代碼:
                       import java.io.FileInputStream;
    /**
     * 使用FileInputStream讀取文件
     */
    public class ReadFile2 {
             public static void main(String[] args) {
                       //聲明流對(duì)象
                       FileInputStream fis = null;                 
                       try{
                                //創(chuàng)建流對(duì)象
                                fis = new FileInputStream("e:\\a.txt");
                                //讀取數(shù)據(jù),并將讀取到的數(shù)據(jù)存儲(chǔ)到數(shù)組中
                                byte[] data = new byte[1024]; //數(shù)據(jù)存儲(chǔ)的數(shù)組
                                int i = fis.read(data);
                               
                                //解析數(shù)據(jù)
                                String s = new String(data,0,i);
                                //輸出字符串
                                System.out.println(s);
                       }catch(Exception e){
                                e.printStackTrace();
                       }finally{
                                try{
                                         //關(guān)閉流,釋放資源
                                         fis.close();
                                }catch(Exception e){}
                       }
             }
    }
             該示例代碼中,只使用一行代碼:
                       int i = fis.read(data);
             就實(shí)現(xiàn)了將流對(duì)象fis中的數(shù)據(jù)讀取到字節(jié)數(shù)組data中。該行代碼的作用是將fis流中的數(shù)據(jù)讀取出來(lái),并依次存儲(chǔ)到數(shù)組data中,返回值為實(shí)際讀取的有效數(shù)據(jù)的個(gè)數(shù)。
             使用該中方式在進(jìn)行讀取時(shí),可以簡(jiǎn)化讀取的代碼。
             當(dāng)然,在讀取文件時(shí),也可以使用Reader類(lèi)的子類(lèi)FileReader進(jìn)行實(shí)現(xiàn),在編寫(xiě)代碼時(shí),只需要將上面示例代碼中的byte數(shù)組替換成char數(shù)組即可。
    使用FileReader讀取文件時(shí),是按照char為單位進(jìn)行讀取的,所以更適合于文本文件的讀取,而對(duì)于二進(jìn)制文件或自定義格式的文件來(lái)說(shuō),還是使用FileInputStream進(jìn)行讀取,方便對(duì)于讀取到的數(shù)據(jù)進(jìn)行解析和操作。
    讀取其它數(shù)據(jù)源的操作和讀取文件類(lèi)似,最大的區(qū)別在于建立流對(duì)象時(shí)選擇的類(lèi)不同,而流對(duì)象一旦建立,則基本的讀取方法是一樣,如果只使用最基本的read方法進(jìn)行讀取,則使用基本上是一致的。這也是IO類(lèi)設(shè)計(jì)的初衷,使得對(duì)于流對(duì)象的操作保持一致,簡(jiǎn)化IO類(lèi)使用的難度。
     程。
             基本的輸出流包含OutputStream和Writer兩個(gè),區(qū)別是OutputStream體系中的類(lèi)(也就是OutputStream的子類(lèi))是按照字節(jié)寫(xiě)入的,而Writer體系中的類(lèi)(也就是Writer的子類(lèi))是按照字符寫(xiě)入的。
             使用輸出流進(jìn)行編程的步驟是:
                       1、建立輸出流
                                建立對(duì)應(yīng)的輸出流對(duì)象,也就是完成由流對(duì)象到外部數(shù)據(jù)源之間的轉(zhuǎn)換。
                       2、向流中寫(xiě)入數(shù)據(jù)
                                將需要輸出的數(shù)據(jù),調(diào)用對(duì)應(yīng)的write方法寫(xiě)入到流對(duì)象中。
                       3、關(guān)閉輸出流
                                在寫(xiě)入完畢以后,調(diào)用流對(duì)象的close方法關(guān)閉輸出流,釋放資源。
             在使用輸出流向外部輸出數(shù)據(jù)時(shí),程序員只需要將數(shù)據(jù)寫(xiě)入流對(duì)象即可,底層的API實(shí)現(xiàn)將流對(duì)象中的內(nèi)容寫(xiě)入外部數(shù)據(jù)源,這個(gè)寫(xiě)入的過(guò)程對(duì)于程序員來(lái)說(shuō)是透明的,不需要專(zhuān)門(mén)書(shū)寫(xiě)代碼實(shí)現(xiàn)。
             在向文件中輸出數(shù)據(jù),也就是寫(xiě)文件時(shí),使用對(duì)應(yīng)的文件輸出流,包括FileOutputStream和FileWriter兩個(gè)類(lèi),下面以FileOutputStream為例子說(shuō)明輸出流的使用。示例代碼如下:
                       import java.io.*;
    /**
     * 使用FileOutputStream寫(xiě)文件示例
     */
    public class WriteFile1 {
             public static void main(String[] args) {
                       String s = "Java語(yǔ)言";
                       int n = 100;
                       //聲明流對(duì)象
                       FileOutputStream fos = null;
                       try{
                                //創(chuàng)建流對(duì)象
                                fos = new FileOutputStream("e:\\out.txt");
                                //轉(zhuǎn)換為byte數(shù)組
                                byte[] b1 = s.getBytes();
                                //換行符
                                byte[] b2 = "\r\n".getBytes();
                                byte[] b3 = String.valueOf(n).getBytes();
                                //依次寫(xiě)入文件
                                fos.write(b1);
                                fos.write(b2);
                                fos.write(b3);
                       } catch (Exception e) {
                                e.printStackTrace();
                       }finally{
                                try{
                                         fos.close();
                                }catch(Exception e){}
                       }
             }
    }
             該示例代碼寫(xiě)入的文件使用記事本打開(kāi)以后,內(nèi)容為:
                       Java語(yǔ)言
    100
             在該示例代碼中,演示了將一個(gè)字符串和一個(gè)int類(lèi)型的值依次寫(xiě)入到同一個(gè)文件中。在寫(xiě)入文件時(shí),首先創(chuàng)建了一個(gè)文件輸出流對(duì)象fos:
                       fos = new FileOutputStream("e:\\out.txt");
             該對(duì)象創(chuàng)建以后,就實(shí)現(xiàn)了從流到外部數(shù)據(jù)源e:\out.txt的連接。說(shuō)明:當(dāng)外部文件不存在時(shí),系統(tǒng)會(huì)自動(dòng)創(chuàng)建該文件,但是如果文件路徑中包含未創(chuàng)建的目錄時(shí)將出現(xiàn)異常。這里書(shū)寫(xiě)的文件路徑可以是絕對(duì)路徑也可以是相對(duì)路徑。
             在 實(shí)際寫(xiě)入文件時(shí),有兩種寫(xiě)入文件的方式:覆蓋和追加。其中“覆蓋”是指清除原文件的內(nèi)容,寫(xiě)入新的內(nèi)容,默認(rèn)采用該種形式寫(xiě)文件,“追加”是指在已有文件 的末尾寫(xiě)入內(nèi)容,保留原來(lái)的文件內(nèi)容,例如寫(xiě)日志文件時(shí),一般采用追加。在實(shí)際使用時(shí)可以根據(jù)需要采用適合的形式,可以使用:
                       public FileOutputStream(String name, boolean append) throws FileNotFoundException
             只需要使用該構(gòu)造方法在構(gòu)造FileOutputStream對(duì)象時(shí),將第二個(gè)參數(shù)append的值設(shè)置為true即可。
             流對(duì)象創(chuàng)建完成以后,就可以使用OutputStream中提供的wirte方法向流中依次寫(xiě)入數(shù)據(jù)了。最基本的寫(xiě)入方法只支持byte數(shù)組格式的數(shù)據(jù),所以如果需要將內(nèi)容寫(xiě)入文件,則需要把對(duì)應(yīng)的內(nèi)容首先轉(zhuǎn)換為byte數(shù)組。
             這里以如下格式寫(xiě)入數(shù)據(jù):首先寫(xiě)入字符串s,使用String類(lèi)的getBytes方法將該字符串轉(zhuǎn)換為byte數(shù)組,然后寫(xiě)入字符串“\r\n”,轉(zhuǎn)換方式同上,該字符串的作用是實(shí)現(xiàn)文本文件的換行顯示,最后寫(xiě)入int數(shù)據(jù)n,首先將n轉(zhuǎn)換為字符串,再轉(zhuǎn)換為byte數(shù)組。這種寫(xiě)入數(shù)據(jù)的順序以及轉(zhuǎn)換為byte數(shù)組的方式就是流的數(shù)據(jù)格式,也就是該文件的格式。因?yàn)檫@里寫(xiě)的都是文本文件,所以寫(xiě)入的內(nèi)容以明文的形式顯示出來(lái),也可以根據(jù)自己需要存儲(chǔ)的數(shù)據(jù)設(shè)定特定的文件格式。
             其實(shí),所有的數(shù)據(jù)文件,包括圖片文件、聲音文件等等,都是以一定的數(shù)據(jù)格式存儲(chǔ)數(shù)據(jù)的,在保存該文件時(shí),將需要保存的數(shù)據(jù)按照該文件的數(shù)據(jù)格式依次寫(xiě)入即可,而在打開(kāi)該文件時(shí),將讀取到的數(shù)據(jù)按照該文件的格式解析成對(duì)應(yīng)的邏輯即可。
             最后,在數(shù)據(jù)寫(xiě)入到流內(nèi)部以后,如果需要立即將寫(xiě)入流內(nèi)部的數(shù)據(jù)強(qiáng)制輸出到外部的數(shù)據(jù)源,則可以使用流對(duì)象的flush方法實(shí)現(xiàn)。如果不需要強(qiáng)制輸出,則只需要在寫(xiě)入結(jié)束以后,關(guān)閉流對(duì)象即可。在關(guān)閉流對(duì)象時(shí),系統(tǒng)首先將流中未輸出到數(shù)據(jù)源中的數(shù)據(jù)強(qiáng)制輸出,然后再釋放該流對(duì)象占用的內(nèi)存空間。
             使用FileWriter寫(xiě)入文件時(shí),步驟和創(chuàng)建流對(duì)象的操作都和該示例代碼一致,只是在轉(zhuǎn)換數(shù)據(jù)時(shí),需要將寫(xiě)入的數(shù)據(jù)轉(zhuǎn)換為char數(shù)組,對(duì)于字符串來(lái)說(shuō),可以使用String中的toCharArray方法實(shí)現(xiàn)轉(zhuǎn)換,然后按照文件格式寫(xiě)入數(shù)據(jù)即可。
             對(duì)于其它類(lèi)型的字節(jié)輸出流/字符輸出流來(lái)說(shuō),只是在邏輯上連接不同的數(shù)據(jù)源,在創(chuàng)建對(duì)象的代碼上會(huì)存在一定的不同,但是一旦流對(duì)象創(chuàng)建完成以后,基本的寫(xiě)入方法都是write方法,也需要首先將需要寫(xiě)入的數(shù)據(jù)按照一定的格式轉(zhuǎn)換為對(duì)應(yīng)的byte數(shù)組/char數(shù)組,然后依次寫(xiě)入即可。
             所以IO類(lèi)的這種設(shè)計(jì)形式,只需要熟悉該體系中的某一個(gè)類(lèi)的使用以后,就可以觸類(lèi)旁通的學(xué)會(huì)其它相同類(lèi)型的類(lèi)的使用,從而簡(jiǎn)化程序員的學(xué)習(xí),使得使用時(shí)保持統(tǒng)一。

    posted @ 2013-09-17 17:51 不高興 閱讀(547) | 評(píng)論 (4)編輯 收藏
     
    Netty 3.1 中文用戶(hù)手冊(cè)(一)-序言 - stone - stone 的博客

    序言


    本指南對(duì)Netty 進(jìn)行了介紹并指出其意義所在。


    1. 問(wèn)題


    現(xiàn)在,我們使用適合一般用途的應(yīng)用或組件來(lái)和彼此通信。例如,我們常常使用一個(gè)HTTP客戶(hù)端從遠(yuǎn)程服務(wù)器獲取信息或者通過(guò)web services進(jìn)行遠(yuǎn)程方法的調(diào)用。

    然而,一個(gè)適合普通目的的協(xié)議或其實(shí)現(xiàn)并不具備其規(guī)模上的擴(kuò)展性。例如,我們無(wú)法使用一個(gè)普通的HTTP服務(wù)器進(jìn)行大型文件,電郵信息的交互,或者處理金 融信息和多人游戲數(shù)據(jù)那種要求準(zhǔn)實(shí)時(shí)消息傳遞的應(yīng)用場(chǎng)景。因此,這些都要求使用一個(gè)適用于特殊目的并經(jīng)過(guò)高度優(yōu)化的協(xié)議實(shí)現(xiàn)。例如,你可能想要實(shí)現(xiàn)一個(gè)對(duì) 基于AJAX的聊天應(yīng)用,媒體流或大文件傳輸進(jìn)行過(guò)特殊優(yōu)化的HTTP服務(wù)器。你甚至可能想去設(shè)計(jì)和實(shí)現(xiàn)一個(gè)全新的,特定于你的需求的通信協(xié)議。

    另一種無(wú)法避免的場(chǎng)景是你可能不得不使用一種專(zhuān)有的協(xié)議和原有系統(tǒng)交互。在這種情況下,你需要考慮的是如何能夠快速的開(kāi)發(fā)出這個(gè)協(xié)議的實(shí)現(xiàn)并且同時(shí)還沒(méi)有犧牲最終應(yīng)用的性能和穩(wěn)定性。


    2. 方案


    Netty 是一個(gè)異步的,事件驅(qū)動(dòng)的網(wǎng)絡(luò)編程框架和工具,使用Netty 可以快速開(kāi)發(fā)出可維護(hù)的,高性能、高擴(kuò)展能力的協(xié)議服務(wù)及其客戶(hù)端應(yīng)用。

    也就是說(shuō),Netty 是一個(gè)基于NIO的客戶(hù),服務(wù)器端編程框架,使用Netty 可以確保你快速和簡(jiǎn)單的開(kāi)發(fā)出一個(gè)網(wǎng)絡(luò)應(yīng)用,例如實(shí)現(xiàn)了某種協(xié)議的客戶(hù),服務(wù)端應(yīng)用。Netty相當(dāng)簡(jiǎn)化和流線化了網(wǎng)絡(luò)應(yīng)用的編程開(kāi)發(fā)過(guò)程,例如,TCP和UDP的socket服務(wù)開(kāi)發(fā)。

    “快速”和“簡(jiǎn)單”并不意味著會(huì)讓你的最終應(yīng)用產(chǎn)生維護(hù)性或性能上的問(wèn)題。Netty 是一個(gè)吸收了多種協(xié)議的實(shí)現(xiàn)經(jīng)驗(yàn),這些協(xié)議包括FTP,SMPT,HTTP,各種二進(jìn)制,文本協(xié)議,并經(jīng)過(guò)相當(dāng)精心設(shè)計(jì)的項(xiàng)目,最終,Netty 成功的找到了一種方式,在保證易于開(kāi)發(fā)的同時(shí)還保證了其應(yīng)用的性能,穩(wěn)定性和伸縮性。

    一些用戶(hù)可能找到了某些同樣聲稱(chēng)具有這些特性的編程框架,因此你們可能想問(wèn)Netty 又有什么不一樣的地方。這個(gè)問(wèn)題的答案是Netty 項(xiàng)目的設(shè)計(jì)哲學(xué)。從創(chuàng)立之初,無(wú)論是在API還是在其實(shí)現(xiàn)上Netty 都致力于為你提供最為舒適的使用體驗(yàn)。雖然這并不是顯而易見(jiàn)的,但你終將會(huì)認(rèn)識(shí)到這種設(shè)計(jì)哲學(xué)將令你在閱讀本指南和使用Netty 時(shí)變得更加得輕松和容易。

    第一章. 開(kāi)始


    這一章節(jié)將圍繞Netty的核心結(jié)構(gòu)展開(kāi),同時(shí)通過(guò)一些簡(jiǎn)單的例子可以讓你更快的了解Netty的使用。當(dāng)你讀完本章,你將有能力使用Netty完成客戶(hù)端和服務(wù)端的開(kāi)發(fā)。

    如果你更喜歡自上而下式的學(xué)習(xí)方式,你可以首先完成 第二章:架構(gòu)總覽 的學(xué)習(xí),然后再回到這里。

    1.1. 開(kāi)始之前


    運(yùn)行本章示例程序的兩個(gè)最低要求是:最新版本的Netty程序以及JDK 1.5或更高版本。最新版本的Netty程序可在項(xiàng)目下載頁(yè) 下載。下載正確版本的JDK,請(qǐng)到你偏好的JDK站點(diǎn)下載。

    這就已經(jīng)足夠了嗎?實(shí)際上你會(huì)發(fā)現(xiàn),這兩個(gè)條件已經(jīng)足夠你完成任何協(xié)議的開(kāi)發(fā)了。如果不是這樣,請(qǐng)聯(lián)系Netty項(xiàng)目社區(qū) ,讓我們知道還缺少了什么。

    最終但不是至少,當(dāng)你想了解本章所介紹的類(lèi)的更多信息時(shí)請(qǐng)參考API手冊(cè)。為方便你的使用,這篇文檔中所有的類(lèi)名均連接至在線API手冊(cè)。此外,如果本篇文檔中有任何錯(cuò)誤信息,無(wú)論是語(yǔ)法錯(cuò)誤,還是打印排版錯(cuò)誤或者你有更好的建議,請(qǐng)不要顧慮,立即聯(lián)系Netty項(xiàng)目社區(qū) 。

    1.2. 拋棄協(xié)議服務(wù)


    在這個(gè)世界上最簡(jiǎn)化的協(xié)議不是“Hello,world!”而是拋棄協(xié)議 。這是一種丟棄接收到的任何數(shù)據(jù)并不做任何回應(yīng)的協(xié)議。

    實(shí)現(xiàn)拋棄協(xié)議(DISCARD protocol),你僅需要忽略接受到的任何數(shù)據(jù)即可。讓我們直接從處理器(handler)實(shí)現(xiàn)開(kāi)始,這個(gè)處理器處理Netty的所有I/O事件。

    Java代碼
    package org.jboss.netty.example.discard;

    @ChannelPipelineCoverage("all")1
    public class DiscardServerHandler extends SimpleChannelHandler {2

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {3
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {4
    e.getCause().printStackTrace();

    Channel ch = e.getChannel();
    ch.close();
    }
    }


    代碼說(shuō)明

    1)ChannelPipelineCoverage注解了一種處理器類(lèi)型,這個(gè)注解標(biāo)示了一個(gè)處理器是 否可被多個(gè)Channel通道共享(同時(shí)關(guān)聯(lián)著ChannelPipeline)。DiscardServerHandler沒(méi)有處理任何有狀態(tài)的信息, 因此這里的注解是“all”。

    2)DiscardServerHandler繼承了SimpleChannelHandler,這也是一個(gè)ChannelHandler 的實(shí)現(xiàn)。SimpleChannelHandler提供了多種你可以重寫(xiě)的事件處理方法。目前直接繼承SimpleChannelHandler已經(jīng)足夠 了,并不需要你完成一個(gè)自己的處理器接口。

    3)我們這里重寫(xiě)了messageReceived事件處理方法。這個(gè)方法由一個(gè)接收了客戶(hù)端傳送數(shù)據(jù)的MessageEvent事件調(diào)用。在這個(gè)例子中,我們忽略接收到的任何數(shù)據(jù),并以此來(lái)實(shí)現(xiàn)一個(gè)拋棄協(xié)議(DISCARD protocol)。

    4)exceptionCaught 事件處理方法由一個(gè)ExceptionEvent異常事件調(diào)用,這個(gè)異常事件起因于Netty的I/O異?;蛞粋€(gè)處理器實(shí)現(xiàn)的內(nèi)部異常。多數(shù)情況下,捕捉 到的異常應(yīng)當(dāng)被記錄下來(lái),并在這個(gè)方法中關(guān)閉這個(gè)channel通道。當(dāng)然處理這種異常情況的方法實(shí)現(xiàn)可能因你的實(shí)際需求而有所不同,例如,在關(guān)閉這個(gè)連 接之前你可能會(huì)發(fā)送一個(gè)包含了錯(cuò)誤碼的響應(yīng)消息。

    目前進(jìn)展不錯(cuò),我們已經(jīng)完成了拋棄協(xié)議服務(wù)器的一半開(kāi)發(fā)工作。下面要做的是完成一個(gè)可以啟動(dòng)這個(gè)包含DiscardServerHandler處理器服務(wù)的主方法。



    Java代碼
    package org.jboss.netty.example.discard;

    import java.net.InetSocketAddress;
    import java.util.concurrent.Executors;

    public class DiscardServer {

    public static void main(String[] args) throws Exception {
    ChannelFactory factory =
    new NioServerSocketChannelFactory (
    Executors.newCachedThreadPool(),
    Executors.newCachedThreadPool());

    ServerBootstrap bootstrap = new ServerBootstrap (factory);

    DiscardServerHandler handler = new DiscardServerHandler();
    ChannelPipeline pipeline = bootstrap.getPipeline();
    pipeline.addLast("handler", handler);

    bootstrap.setOption("child.tcpNoDelay", true);
    bootstrap.setOption("child.keepAlive", true);

    bootstrap.bind(new InetSocketAddress(8080));
    }
    }


    代碼說(shuō)明


    1)ChannelFactory 是一個(gè)創(chuàng)建和管理Channel通道及其相關(guān)資源的工廠接口,它處理所有的I/O請(qǐng)求并產(chǎn)生相應(yīng)的I/O ChannelEvent通道事件。Netty 提供了多種 ChannelFactory 實(shí)現(xiàn)。這里我們需要實(shí)現(xiàn)一個(gè)服務(wù)端的例子,因此我們使用NioServerSocketChannelFactory實(shí)現(xiàn)。另一件需要注意的事情是這個(gè)工 廠并自己不負(fù)責(zé)創(chuàng)建I/O線程。你應(yīng)當(dāng)在其構(gòu)造器中指定該工廠使用的線程池,這樣做的好處是你獲得了更高的控制力來(lái)管理你的應(yīng)用環(huán)境中使用的線程,例如一 個(gè)包含了安全管理的應(yīng)用服務(wù)。

    2)ServerBootstrap 是一個(gè)設(shè)置服務(wù)的幫助類(lèi)。你甚至可以在這個(gè)服務(wù)中直接設(shè)置一個(gè)Channel通道。然而請(qǐng)注意,這是一個(gè)繁瑣的過(guò)程,大多數(shù)情況下并不需要這樣做。

    3)這里,我們將DiscardServerHandler處理器添加至默認(rèn)的ChannelPipeline通道。任何時(shí)候當(dāng)服務(wù)器接收到一個(gè)新的連 接,一個(gè)新的ChannelPipeline管道對(duì)象將被創(chuàng)建,并且所有在這里添加的ChannelHandler對(duì)象將被添加至這個(gè)新的 ChannelPipeline管道對(duì)象。這很像是一種淺拷貝操作(a shallow-copy operation);所有的Channel通道以及其對(duì)應(yīng)的ChannelPipeline實(shí)例將分享相同的DiscardServerHandler 實(shí)例。

    4)你也可以設(shè)置我們?cè)谶@里指定的這個(gè)通道實(shí)現(xiàn)的配置參數(shù)。我們正在寫(xiě)的是一個(gè)TCP/IP服務(wù),因此我們運(yùn)行設(shè)定一些socket選項(xiàng),例如 tcpNoDelay和keepAlive。請(qǐng)注意我們?cè)谂渲眠x項(xiàng)里添加的"child."前綴。這意味著這個(gè)配置項(xiàng)僅適用于我們接收到的通道實(shí)例,而不 是ServerSocketChannel實(shí)例。因此,你可以這樣給一個(gè)ServerSocketChannel設(shè)定參數(shù):
    bootstrap.setOption("reuseAddress", true);

    5)我們繼續(xù)。剩下要做的是綁定這個(gè)服務(wù)使用的端口并且啟動(dòng)這個(gè)服務(wù)。這里,我們綁定本機(jī)所有網(wǎng)卡(NICs,network interface cards)上的8080端口。當(dāng)然,你現(xiàn)在也可以對(duì)應(yīng)不同的綁定地址多次調(diào)用綁定操作。

    大功告成!現(xiàn)在你已經(jīng)完成你的第一個(gè)基于Netty的服務(wù)端程序。

    1.3. 查看接收到的數(shù)據(jù)


    現(xiàn)在你已經(jīng)完成了你的第一個(gè)服務(wù)端程序,我們需要測(cè)試它是否可以真正的工作。最簡(jiǎn)單的方法是使用telnet 命令。例如,你可以在命令行中輸入“telnet localhost 8080 ”或其他類(lèi)型參數(shù)。

    然而,我們可以認(rèn)為服務(wù)器在正常工作嗎?由于這是一個(gè)丟球協(xié)議服務(wù),所以實(shí)際上我們無(wú)法真正的知道。你最終將收不到任何回應(yīng)。為了證明它在真正的工作,讓我們修改代碼打印其接收到的數(shù)據(jù)。
    我們已經(jīng)知道當(dāng)完成數(shù)據(jù)的接收后將產(chǎn)生MessageEvent消息事件,并且也會(huì)觸發(fā)messageReceived處理方法。所以讓我在DiscardServerHandler處理器的messageReceived方法內(nèi)增加一些代碼。

    Java代碼
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
    ChannelBuffer buf = (ChannelBuffer) e.getMessage();
    while(buf.readable()) {
    System.out.println((char) buf.readByte());
    }
    }


    代碼說(shuō)明

    1) 基本上我們可以假定在socket的傳輸中消息類(lèi)型總是ChannelBuffer。ChannelBuffer是Netty的一個(gè)基本數(shù)據(jù)結(jié)構(gòu),這個(gè)數(shù) 據(jù)結(jié)構(gòu)存儲(chǔ)了一個(gè)字節(jié)序列。ChannelBuffer類(lèi)似于NIO的ByteBuffer,但是前者卻更加的靈活和易于使用。例如,Netty允許你創(chuàng) 建一個(gè)由多個(gè)ChannelBuffer構(gòu)建的復(fù)合ChannelBuffer類(lèi)型,這樣就可以減少不必要的內(nèi)存拷貝次數(shù)。

    2) 雖然ChannelBuffer有些類(lèi)似于NIO的ByteBuffer,但強(qiáng)烈建議你參考Netty的API手冊(cè)。學(xué)會(huì)如何正確的使用ChannelBuffer是無(wú)障礙使用Netty的關(guān)鍵一步。

    如果你再次運(yùn)行telnet命令,你將會(huì)看到你所接收到的數(shù)據(jù)。
    拋棄協(xié)議服務(wù)的所有源代碼均存放在在分發(fā)版的org.jboss.netty.example.discard包下。


    1.4. 響應(yīng)協(xié)議服務(wù)


    目前,我們雖然使用了數(shù)據(jù),但最終卻未作任何回應(yīng)。然而一般情況下,一個(gè)服務(wù)都需要回應(yīng)一個(gè)請(qǐng)求。讓我們實(shí)現(xiàn)ECHO協(xié)議 來(lái)學(xué)習(xí)如何完成一個(gè)客戶(hù)請(qǐng)求的回應(yīng)消息,ECHO協(xié)議規(guī)定要返回任何接收到的數(shù)據(jù)。

    與我們上一節(jié)實(shí)現(xiàn)的拋棄協(xié)議服務(wù)唯一不同的地方是,這里需要返回所有的接收數(shù)據(jù)而不是僅僅打印在控制臺(tái)之上。因此我們?cè)俅涡薷膍essageReceived方法就足夠了。

    Java代碼
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
    Channel ch = e.getChannel();
    ch.write(e.getMessage());
    }
    代碼說(shuō)明
    1) 一個(gè)ChannelEvent通道事件對(duì)象自身存有一個(gè)和其關(guān)聯(lián)的Channel對(duì)象引用。這個(gè)返回的Channel通道對(duì)象代表了這個(gè)接收 MessageEvent消息事件的連接(connection)。因此,我們可以通過(guò)調(diào)用這個(gè)Channel通道對(duì)象的write方法向遠(yuǎn)程節(jié)點(diǎn)寫(xiě)入返 回?cái)?shù)據(jù)。

    現(xiàn)在如果你再次運(yùn)行telnet命令,你將會(huì)看到服務(wù)器返回的你所發(fā)送的任何數(shù)據(jù)。

    相應(yīng)服務(wù)的所有源代碼存放在分發(fā)版的org.jboss.netty.example.echo包下。

    1.5. 時(shí)間協(xié)議服務(wù)


    這一節(jié)需要實(shí)現(xiàn)的協(xié)議是TIME協(xié)議 。這是一個(gè)與先前所介紹的不同的例子。這個(gè)例子里,服務(wù)端返回一個(gè)32位的整數(shù)消息,我們不接受請(qǐng)求中包含的任何數(shù)據(jù)并且當(dāng)消息返回完畢后立即關(guān)閉連接。通過(guò)這個(gè)例子你將學(xué)會(huì)如何構(gòu)建和發(fā)送消息,以及當(dāng)完成處理后如何主動(dòng)關(guān)閉連接。

    因?yàn)槲覀儠?huì)忽略接收到的任何數(shù)據(jù)而只是返回消息,這應(yīng)當(dāng)在建立連接后就立即開(kāi)始。因此這次我們不再使用messageReceived方法,取而代之的是使用channelConnected方法。下面是具體的實(shí)現(xiàn):



    Java代碼
    package org.jboss.netty.example.time;

    @ChannelPipelineCoverage("all")
    public class TimeServerHandler extends SimpleChannelHandler {

    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
    Channel ch = e.getChannel();

    ChannelBuffer time = ChannelBuffers.buffer(4);
    time.writeInt(System.currentTimeMillis() / 1000);

    ChannelFuture f = ch.write(time);

    f.addListener(new ChannelFutureListener() {
    public void operationComplete(ChannelFuture future) {
    Channel ch = future.getChannel();
    ch.close();
    }
    });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
    e.getCause().printStackTrace();
    e.getChannel().close();
    }
    }

    代碼說(shuō)明
    1) 正如我們解釋過(guò)的,channelConnected方法將在一個(gè)連接建立后立即觸發(fā)。因此讓我們?cè)谶@個(gè)方法里完成一個(gè)代表當(dāng)前時(shí)間(秒)的32位整數(shù)消息的構(gòu)建工作。

    2) 為了發(fā)送一個(gè)消息,我們需要分配一個(gè)包含了這個(gè)消息的buffer緩沖。因?yàn)槲覀儗⒁獙?xiě)入一個(gè)32位的整數(shù),因此我們需要一個(gè)4字節(jié)的 ChannelBuffer。ChannelBuffers是一個(gè)可以創(chuàng)建buffer緩沖的幫助類(lèi)。除了這個(gè)buffer方 法,ChannelBuffers還提供了很多和ChannelBuffer相關(guān)的實(shí)用方法。更多信息請(qǐng)參考API手冊(cè)。

    另外,一個(gè)很不錯(cuò)的方法是使用靜態(tài)的導(dǎo)入方式:
    import static org.jboss.netty.buffer.ChannelBuffers.*;
    ...
    ChannelBuffer dynamicBuf = dynamicBuffer(256);
    ChannelBuffer ordinaryBuf = buffer(1024);

    3) 像通常一樣,我們需要自己構(gòu)造消息。

    但是打住,flip在哪?過(guò)去我們?cè)谑褂肗IO發(fā)送消息時(shí)不是常常需要調(diào)用 ByteBuffer.flip()方法嗎?實(shí)際上ChannelBuffer之所以不需要這個(gè)方法是因?yàn)?ChannelBuffer有兩個(gè)指針;一個(gè)對(duì)應(yīng)讀操作,一個(gè)對(duì)應(yīng)寫(xiě)操作。當(dāng)你向一個(gè) ChannelBuffer寫(xiě)入數(shù)據(jù)的時(shí)候?qū)懼羔樀乃饕当銜?huì)增加,但與此同時(shí)讀指針的索引值不會(huì)有任何變化。讀寫(xiě)指針的索引值分別代表了這個(gè)消息的開(kāi) 始、結(jié)束位置。

    與之相應(yīng)的是,NIO的buffer緩沖沒(méi)有為我們提供如此簡(jiǎn)潔的一種方法,除非你調(diào)用它的flip方法。因此,當(dāng)你忘記調(diào)用flip方法而引起發(fā)送錯(cuò)誤 時(shí),你便會(huì)陷入困境。這樣的錯(cuò)誤不會(huì)再Netty中發(fā)生,因?yàn)槲覀儗?duì)應(yīng)不同的操作類(lèi)型有不同的指針。你會(huì)發(fā)現(xiàn)就像你已習(xí)慣的這樣過(guò)程變得更加容易—一種沒(méi) 有flippling的體驗(yàn)!

    另一點(diǎn)需要注意的是這個(gè)寫(xiě)方法返回了一個(gè)ChannelFuture對(duì)象。一個(gè)ChannelFuture 對(duì)象代表了一個(gè)尚未發(fā)生的I/O操作。這意味著,任何已請(qǐng)求的操作都可能是沒(méi)有被立即執(zhí)行的,因?yàn)樵贜etty內(nèi)部所有的操作都是異步的。例如,下面的代 碼可能會(huì)關(guān)閉一 個(gè)連接,這個(gè)操作甚至?xí)l(fā)生在消息發(fā)送之前:

    Channel ch = ...;
    ch.write(message);
    ch.close();

    因此,你需要這個(gè)write方法返回的ChannelFuture對(duì)象,close方法需要等待寫(xiě)操作異步完成之后的ChannelFuture通知/監(jiān)聽(tīng)觸發(fā)。需要注意的是,關(guān)閉方法仍舊不是立即關(guān)閉一個(gè)連接,它同樣也是返回了一個(gè)ChannelFuture對(duì)象。

    4) 在寫(xiě)操作完成之后我們又如何得到通知?這個(gè)只需要簡(jiǎn)單的為這個(gè)返回的ChannelFuture對(duì)象增加一個(gè)ChannelFutureListener 即可。在這里我們創(chuàng)建了一個(gè)匿名ChannelFutureListener對(duì)象,在這個(gè)ChannelFutureListener對(duì)象內(nèi)部我們處理了 異步操作完成之后的關(guān)閉操作。

    另外,你也可以通過(guò)使用一個(gè)預(yù)定義的監(jiān)聽(tīng)類(lèi)來(lái)簡(jiǎn)化代碼。
    f.addListener(ChannelFutureListener.CLOSE);


    1.6. 時(shí)間協(xié)議服務(wù)客戶(hù)端


    不同于DISCARD和ECHO協(xié)議服務(wù),我們需要一個(gè)時(shí)間協(xié)議服務(wù)的客戶(hù)端,因?yàn)槿藗儫o(wú)法直接將一個(gè)32位的二進(jìn)制數(shù)據(jù)轉(zhuǎn)換一個(gè)日歷時(shí)間。在這一節(jié)我們將學(xué)習(xí)如何確保服務(wù)器端工作正常,以及如何使用Netty完成客戶(hù)端的開(kāi)發(fā)。

    使用Netty開(kāi)發(fā)服務(wù)器端和客戶(hù)端代碼最大的不同是要求使用不同的Bootstrap及ChannelFactory。請(qǐng)參照以下的代碼:

    Java代碼
    package org.jboss.netty.example.time;

    import java.net.InetSocketAddress;
    import java.util.concurrent.Executors;

    public class TimeClient {

    public static void main(String[] args) throws Exception {
    String host = args[0];
    int port = Integer.parseInt(args[1]);

    ChannelFactory factory =
    new NioClientSocketChannelFactory (
    Executors.newCachedThreadPool(),
    Executors.newCachedThreadPool());

    ClientBootstrap bootstrap = new ClientBootstrap (factory);

    TimeClientHandler handler = new TimeClientHandler();
    bootstrap.getPipeline().addLast("handler", handler);

    bootstrap.setOption("tcpNoDelay" , true);
    bootstrap.setOption("keepAlive", true);

    bootstrap.connect (new InetSocketAddress(host, port));
    }
    }
    代碼說(shuō)明
    1) 使用NioClientSocketChannelFactory而不是NioServerSocketChannelFactory來(lái)創(chuàng)建客戶(hù)端的Channel通道對(duì)象。

    2) 客戶(hù)端的ClientBootstrap對(duì)應(yīng)ServerBootstrap。

    3) 請(qǐng)注意,這里不存在使用“child.”前綴的配置項(xiàng),客戶(hù)端的SocketChannel實(shí)例不存在父級(jí)Channel對(duì)象。

    4) 我們應(yīng)當(dāng)調(diào)用connect連接方法,而不是之前的bind綁定方法。

    正如你所看到的,這與服務(wù)端的啟動(dòng)過(guò)程是完全不一樣的。ChannelHandler又該如何實(shí)現(xiàn)呢?它應(yīng)當(dāng)負(fù)責(zé)接收一個(gè)32位的整數(shù),將其轉(zhuǎn)換為可讀的格式后,打印輸出時(shí)間,并關(guān)閉這個(gè)連接。



    Java代碼
    package org.jboss.netty.example.time;

    import java.util.Date;

    @ChannelPipelineCoverage("all")
    public class TimeClientHandler extends SimpleChannelHandler {

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
    ChannelBuffer buf = (ChannelBuffer) e.getMessage();
    long currentTimeMillis = buf.readInt() * 1000L;
    System.out.println(new Date(currentTimeMillis));
    e.getChannel().close();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
    e.getCause().printStackTrace();
    e.getChannel().close();
    }
    }

    這看起來(lái)很是簡(jiǎn)單,與服務(wù)端的實(shí)現(xiàn)也并未有什么不同。然而,這個(gè)處理器卻時(shí)常會(huì)因?yàn)閽伋鯥ndexOutOfBoundsException異常而拒絕工作。我們將在下一節(jié)討論這個(gè)問(wèn)題產(chǎn)生的原因。

    1.7. 流數(shù)據(jù)的傳輸處理



    1.7.1. Socket Buffer的缺陷


    對(duì)于例如TCP/IP這種基于流的傳輸協(xié)議實(shí)現(xiàn),接收到的數(shù)據(jù)會(huì)被存儲(chǔ)在socket的接受緩沖區(qū)內(nèi)。不幸的是,這種基于流的傳輸緩沖區(qū)并不是一個(gè)包隊(duì) 列,而是一個(gè)字節(jié)隊(duì)列。這意味著,即使你以?xún)蓚€(gè)數(shù)據(jù)包的形式發(fā)送了兩條消息,操作系統(tǒng)卻不會(huì)把它們看成是兩條消息,而僅僅是一個(gè)批次的字節(jié)序列。因此,在 這種情況下我們就無(wú)法保證收到的數(shù)據(jù)恰好就是遠(yuǎn)程節(jié)點(diǎn)所發(fā)送的數(shù)據(jù)。例如,讓我們假設(shè)一個(gè)操作系統(tǒng)的TCP/IP堆棧收到了三個(gè)數(shù)據(jù)包:



    +-----+-----+-----+
    | ABC | DEF | GHI |
    +-----+-----+-----+

    由于這種流傳輸協(xié)議的普遍性質(zhì),在你的應(yīng)用中有較高的可能會(huì)把這些數(shù)據(jù)讀取為另外一種形式:



    +----+-------+---+---+
    | AB | CDEFG | H | I |
    +----+-------+---+---+


    因此對(duì)于數(shù)據(jù)的接收方,不管是服務(wù)端還是客戶(hù)端,應(yīng)當(dāng)重構(gòu)這些接收到的數(shù)據(jù),讓其變成一種可讓你的應(yīng)用邏輯易于理解的更有意義的數(shù)據(jù)結(jié)構(gòu)。在上面所述的這個(gè)例子中,接收到的數(shù)據(jù)應(yīng)當(dāng)重構(gòu)為下面的形式:



    +-----+-----+-----+
    | ABC | DEF | GHI |
    +-----+-----+-----+

    1.7.2. 第一種方案


    現(xiàn)在讓我們回到時(shí)間協(xié)議服務(wù)客戶(hù)端的例子中。我們?cè)谶@里遇到了同樣的問(wèn)題。一個(gè)32位的整數(shù)是一個(gè)非常小的數(shù)據(jù)量,因此它常常不會(huì)被切分在不同的數(shù)據(jù)段內(nèi)。然而,問(wèn)題是它確實(shí)可以被切分在不同的數(shù)據(jù)段內(nèi),并且這種可能性隨著流量的增加而提高。

    最簡(jiǎn)單的方案是在程序內(nèi)部創(chuàng)建一個(gè)可準(zhǔn)確接收4字節(jié)數(shù)據(jù)的累積性緩沖。下面的代碼是修復(fù)了這個(gè)問(wèn)題后的TimeClientHandler實(shí)現(xiàn)。



    Java代碼
    package org.jboss.netty.example.time;

    import static org.jboss.netty.buffer.ChannelBuffers.*;

    import java.util.Date;

    @ChannelPipelineCoverage("one")
    public class TimeClientHandler extends SimpleChannelHandler {

    private final ChannelBuffer buf = dynamicBuffer();

    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
    ChannelBuffer m = (ChannelBuffer) e.getMessage();
    buf.writeBytes(m);

    if (buf.readableBytes() >= 4) {
    long currentTimeMillis = buf.readInt() * 1000L;
    System.out.println(new Date(currentTimeMillis));
    e.getChannel().close();
    }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) {
    e.getCause().printStackTrace();
    e.getChannel().close();
    }
    }


    代碼說(shuō)明

    1) 這一次我們使用“one”做為ChannelPipelineCoverage的注解值。這是由于這個(gè)修改后的TimeClientHandler不在不 在內(nèi)部保持一個(gè)buffer緩沖,因此這個(gè)TimeClientHandler實(shí)例不可以再被多個(gè)Channel通道或ChannelPipeline共 享。否則這個(gè)內(nèi)部的buffer緩沖將無(wú)法緩沖正確的數(shù)據(jù)內(nèi)容。

    2) 動(dòng)態(tài)的buffer緩沖也是ChannelBuffer的一種實(shí)現(xiàn),其擁有動(dòng)態(tài)增加緩沖容量的能力。當(dāng)你無(wú)法預(yù)估消息的數(shù)據(jù)長(zhǎng)度時(shí),動(dòng)態(tài)的buffer緩沖是一種很有用的緩沖結(jié)構(gòu)。

    3) 首先,所有的數(shù)據(jù)將會(huì)被累積的緩沖至buf容器。

    4) 之后,這個(gè)處理器將會(huì)檢查是否收到了足夠的數(shù)據(jù)然后再進(jìn)行真實(shí)的業(yè)務(wù)邏輯處理,在這個(gè)例子中需要接收4字節(jié)數(shù)據(jù)。否則,Netty將重復(fù)調(diào)用messageReceived方法,直至4字節(jié)數(shù)據(jù)接收完成。

    這里還有另一個(gè)地方需要進(jìn)行修改。你是否還記得我們把TimeClientHandler實(shí)例添加到了這個(gè)ClientBootstrap實(shí)例的默 認(rèn)ChannelPipeline管道里?這意味著同一個(gè)TimeClientHandler實(shí)例將被多個(gè)Channel通道共享,因此接受的數(shù)據(jù)也將受 到破壞。為了給每一個(gè)Channel通道創(chuàng)建一個(gè)新的TimeClientHandler實(shí)例,我們需要實(shí)現(xiàn)一個(gè) ChannelPipelineFactory管道工廠:

    Java代碼
    package org.jboss.netty.example.time;

    public class TimeClientPipelineFactory implements ChannelPipelineFactory {

    public ChannelPipeline getPipeline() {
    ChannelPipeline pipeline = Channels.pipeline();
    pipeline.addLast("handler", new TimeClientHandler());
    return pipeline;
    }
    }

    現(xiàn)在,我們需要把TimeClient下面的代碼片段:

    Java代碼
    TimeClientHandler handler = new TimeClientHandler();
    bootstrap.getPipeline().addLast("handler", handler);

    替換為:

    Java代碼
    bootstrap.setPipelineFactory(new TimeClientPipelineFactory());

    雖然這看上去有些復(fù)雜,并且由于在TimeClient內(nèi)部我們只創(chuàng)建了一個(gè)連接(connection),因此我們?cè)谶@里確實(shí)沒(méi)必要引入TimeClientPipelineFactory實(shí)例。

    然而,當(dāng)你的應(yīng)用變得越來(lái)越復(fù)雜,你就總會(huì)需要實(shí)現(xiàn)自己的ChannelPipelineFactory,這個(gè)管道工廠將會(huì)令你的管道配置變得更加具有靈活性。

    1.7.3. 第二種方案



    雖然第二種方案解決了時(shí)間協(xié)議客戶(hù)端遇到的問(wèn)題,但是這個(gè)修改后的處理器實(shí)現(xiàn)看上去卻不再那么簡(jiǎn)潔。設(shè)想一種更為復(fù)雜的,由多個(gè)可變長(zhǎng)度字段組成的協(xié)議。你的ChannelHandler實(shí)現(xiàn)將變得越來(lái)越難以維護(hù)。

    正如你已注意到的,你可以為一個(gè)ChannelPipeline添加多個(gè)ChannelHandler,因此,為了減小應(yīng)用的復(fù)雜性,你可以把這個(gè)臃腫的 ChannelHandler切分為多個(gè)獨(dú)立的模塊單元。例如,你可以把TimeClientHandler切分為兩個(gè)獨(dú)立的處理器:

    TimeDecoder,解決數(shù)據(jù)分段的問(wèn)題。
    TimeClientHandler,原始版本的實(shí)現(xiàn)。
    幸運(yùn)的是,Netty提供了一個(gè)可擴(kuò)展的類(lèi),這個(gè)類(lèi)可以直接拿過(guò)來(lái)使用幫你完成TimeDecoder的開(kāi)發(fā):

    Java代碼
    package org.jboss.netty.example.time;


    public class TimeDecoder extends FrameDecoder {

    @Override
    protected Object decode(
    ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {

    if (buffer.readableBytes() < 4) {
    return null;
    }

    return buffer.readBytes(4);
    }
    }


    代碼說(shuō)明
    1) 這里不再需要使用ChannelPipelineCoverage的注解,因?yàn)镕rameDecoder總是被注解為“one”。

    2) 當(dāng)接收到新的數(shù)據(jù)后,F(xiàn)rameDecoder會(huì)調(diào)用decode方法,同時(shí)傳入一個(gè)FrameDecoder內(nèi)部持有的累積型buffer緩沖。

    3) 如果decode返回null值,這意味著還沒(méi)有接收到足夠的數(shù)據(jù)。當(dāng)有足夠數(shù)量的數(shù)據(jù)后FrameDecoder會(huì)再次調(diào)用decode方法。

    4) 如果decode方法返回一個(gè)非空值,這意味著decode方法已經(jīng)成功完成一條信息的解碼。FrameDecoder將丟棄這個(gè)內(nèi)部的累計(jì)型緩沖。請(qǐng)注 意你不需要對(duì)多條消息進(jìn)行解碼,F(xiàn)rameDecoder將保持對(duì)decode方法的調(diào)用,直到decode方法返回非空對(duì)象。

    如果你是一個(gè)勇于嘗試的人,你或許應(yīng)當(dāng)使用ReplayingDecoder,ReplayingDecoder更加簡(jiǎn)化了解碼的過(guò)程。為此你需要查看API手冊(cè)獲得更多的幫助信息。

    Java代碼
    package org.jboss.netty.example.time;

    public class TimeDecoder extends ReplayingDecoder<VoidEnum> {

    @Override
    protected Object decode(
    ChannelHandlerContext ctx, Channel channel,
    ChannelBuffer buffer, VoidEnum state) {

    return buffer.readBytes(4);
    }
    }

    此外,Netty還為你提供了一些可以直接使用的decoder實(shí)現(xiàn),這些decoder實(shí)現(xiàn)不僅可以讓你非常容易的實(shí)現(xiàn)大多數(shù)協(xié)議,并且還會(huì)幫你避免某些臃腫、難以維護(hù)的處理器實(shí)現(xiàn)。請(qǐng)參考下面的代碼包獲得更加詳細(xì)的實(shí)例:

    org.jboss.netty.example.factorial for a binary protocol, and
    org.jboss.netty.example.telnet for a text line-based protocol
    1.8. 使用POJO代替ChannelBuffer


    目前為止所有的實(shí)例程序都是使用ChannelBuffer做為協(xié)議消息的原始數(shù)據(jù)結(jié)構(gòu)。在這一節(jié),我們將改進(jìn)時(shí)間協(xié)議服務(wù)的客戶(hù)/服務(wù)端實(shí)現(xiàn),使用POJO 而不是ChannelBuffer做為協(xié)議消息的原始數(shù)據(jù)結(jié)構(gòu)。

    在你的ChannelHandler實(shí)現(xiàn)中使用POJO的優(yōu)勢(shì)是很明顯的;從你的ChannelHandler實(shí)現(xiàn)中分離從ChannelBuffer獲 取數(shù)據(jù)的代碼,將有助于提高你的ChannelHandler實(shí)現(xiàn)的可維護(hù)性和可重用性。在時(shí)間協(xié)議服務(wù)的客戶(hù)/服務(wù)端代碼中,直接使用 ChannelBuffer讀取一個(gè)32位的整數(shù)并不是一個(gè)主要的問(wèn)題。然而,你會(huì)發(fā)現(xiàn),當(dāng)你試圖實(shí)現(xiàn)一個(gè)真實(shí)的協(xié)議的時(shí)候,這種代碼上的分離是很有必要 的。

    首先,讓我們定義一個(gè)稱(chēng)之為UnixTime的新類(lèi)型。

    Java代碼
    package org.jboss.netty.example.time;

    import java.util.Date;

    public class UnixTime {
    private final int value;

    public UnixTime(int value) {
    this.value = value;
    }

    public int getValue() {
    return value;
    }

    @Override
    public String toString() {
    return new Date(value * 1000L).toString();
    }
    }

    現(xiàn)在讓我們重新修改TimeDecoder實(shí)現(xiàn),讓其返回一個(gè)UnixTime,而不是一個(gè)ChannelBuffer。

    Java代碼
    @Override
    protected Object decode(
    ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
    if (buffer.readableBytes() < 4) {
    return null;
    }

    return new UnixTime(buffer.readInt());
    }

    FrameDecoder和ReplayingDecoder允許你返回一個(gè)任何類(lèi)型的對(duì)象。如果它們僅允許返回一個(gè)ChannelBuffer類(lèi) 型的對(duì)象,我們將不得不插入另一個(gè)可以從ChannelBuffer對(duì)象轉(zhuǎn)換 為UnixTime對(duì)象的ChannelHandler實(shí)現(xiàn)。


    有了這個(gè)修改后的decoder實(shí)現(xiàn),這個(gè)TimeClientHandler便不會(huì)再依賴(lài)ChannelBuffer。

    Java代碼
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
    UnixTime m = (UnixTime) e.getMessage();
    System.out.println(m);
    e.getChannel().close();
    }

    更加簡(jiǎn)單優(yōu)雅了,不是嗎?同樣的技巧也可以應(yīng)用在服務(wù)端,讓我們現(xiàn)在更新TimeServerHandler的實(shí)現(xiàn):

    Java代碼
    @Override
    public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e) {
    UnixTime time = new UnixTime(System.currentTimeMillis() / 1000);
    ChannelFuture f = e.getChannel().write(time);
    f.addListener(ChannelFutureListener.CLOSE);
    }

    現(xiàn)在剩下的唯一需要修改的部分是這個(gè)ChannelHandler實(shí)現(xiàn),這個(gè)ChannelHandler實(shí)現(xiàn)需要把一個(gè)UnixTime對(duì)象重新 轉(zhuǎn)換為一個(gè)ChannelBuffer。但這卻已是相當(dāng)簡(jiǎn)單了,因?yàn)楫?dāng)你對(duì)消息進(jìn)行編碼的時(shí)候你不再需要處理數(shù)據(jù)包的拆分及組裝。

    Java代碼
    package org.jboss.netty.example.time;

    import static org.jboss.netty.buffer.ChannelBuffers.*;

    @ChannelPipelineCoverage("all")
    public class TimeEncoder extends SimpleChannelHandler {

    public void writeRequested(ChannelHandlerContext ctx, MessageEvent e) {
    UnixTime time = (UnixTime) e.getMessage();

    ChannelBuffer buf = buffer(4);
    buf.writeInt(time.getValue());

    Channels.write(ctx, e.getFuture(), buf);
    }
    }

    代碼說(shuō)明

    1) 因?yàn)檫@個(gè)encoder是無(wú)狀態(tài)的,所以其使用的ChannelPipelineCoverage注解值是“all”。實(shí)際上,大多數(shù)encoder實(shí)現(xiàn)都是無(wú)狀態(tài)的。

    2) 一個(gè)encoder通過(guò)重寫(xiě)writeRequested方法來(lái)實(shí)現(xiàn)對(duì)寫(xiě)操作請(qǐng)求的攔截。不過(guò)請(qǐng)注意雖然這個(gè)writeRequested方法使用了和 messageReceived方法一樣的MessageEvent參數(shù),但是它們卻分別對(duì)應(yīng)了不同的解釋。一個(gè)ChannelEvent事件可以既是一 個(gè)上升流事件(upstream event)也可以是一個(gè)下降流事件(downstream event),這取決于事件流的方向。例如:一個(gè)MessageEvent消息事件可以作為一個(gè)上升流事件(upstream event)被messageReceived方法調(diào)用,也可以作為一個(gè)下降流事件(downstream event)被writeRequested方法調(diào)用。請(qǐng)參考API手冊(cè)獲得上升流事件(upstream event)和下降流事件(downstream event)的更多信息。

    3) 一旦完成了POJO和ChannelBuffer轉(zhuǎn)換,你應(yīng)當(dāng)確保把這個(gè)新的buffer緩沖轉(zhuǎn)發(fā)至先前的 ChannelDownstreamHandler處理,這個(gè)下降通道的處理器由某個(gè)ChannelPipeline管理。Channels提供了多個(gè)可 以創(chuàng)建和發(fā)送ChannelEvent事件的幫助方法。在這個(gè)例子中,Channels.write(...)方法創(chuàng)建了一個(gè)新的 MessageEvent事件,并把這個(gè)事件發(fā)送給了先前的處于某個(gè)ChannelPipeline內(nèi)的 ChannelDownstreamHandler處理器。

    另外,一個(gè)很不錯(cuò)的方法是使用靜態(tài)的方式導(dǎo)入Channels類(lèi):

    import static org.jboss.netty.channel.Channels.*;
    ...
    ChannelPipeline pipeline = pipeline();
    write(ctx, e.getFuture(), buf);
    fireChannelDisconnected(ctx);


    最后的任務(wù)是把這個(gè)TimeEncoder插入服務(wù)端的ChannelPipeline,這是一個(gè)很簡(jiǎn)單的步驟。

    1.9. 關(guān)閉你的應(yīng)用


    如果你運(yùn)行了TimeClient,你肯定可以注意到,這個(gè)應(yīng)用并沒(méi)有自動(dòng)退出而只是在那里保持著無(wú)意義的運(yùn)行。跟蹤堆棧記錄你可以發(fā)現(xiàn),這里有一些運(yùn)行 狀態(tài)的I/O線程。為了關(guān)閉這些I/O線程并讓?xiě)?yīng)用優(yōu)雅的退出,你需要釋放這些由ChannelFactory分配的資源。

    一個(gè)典型的網(wǎng)絡(luò)應(yīng)用的關(guān)閉過(guò)程由以下三步組成:

    關(guān)閉負(fù)責(zé)接收所有請(qǐng)求的server socket。
    關(guān)閉所有客戶(hù)端socket或服務(wù)端為響應(yīng)某個(gè)請(qǐng)求而創(chuàng)建的socket。
    釋放ChannelFactory使用的所有資源。
    為了讓TimeClient執(zhí)行這三步,你需要在TimeClient.main()方法內(nèi)關(guān)閉唯一的客戶(hù)連接以及ChannelFactory使用的所有資源,這樣做便可以?xún)?yōu)雅的關(guān)閉這個(gè)應(yīng)用。

    Java代碼
    package org.jboss.netty.example.time;

    public class TimeClient {
    public static void main(String[] args) throws Exception {
    ...
    ChannelFactory factory = ...;
    ClientBootstrap bootstrap = ...;
    ...
    ChannelFuture future = bootstrap.connect(...);
    future.awaitUninterruptible();
    if (!future.isSuccess()) {
    future.getCause().printStackTrace();
    }
    future.getChannel().getCloseFuture().awaitUninterruptibly();
    factory.releaseExternalResources();
    }
    }
    代碼說(shuō)明
    1) ClientBootstrap對(duì)象的connect方法返回一個(gè)ChannelFuture對(duì)象,這個(gè)ChannelFuture對(duì)象將告知這個(gè)連接操 作的成功或失敗狀態(tài)。同時(shí)這個(gè)ChannelFuture對(duì)象也保存了一個(gè)代表這個(gè)連接操作的Channel對(duì)象引用。

    2) 阻塞式的等待,直到ChannelFuture對(duì)象返回這個(gè)連接操作的成功或失敗狀態(tài)。

    3) 如果連接失敗,我們將打印連接失敗的原因。如果連接操作沒(méi)有成功或者被取消,ChannelFuture對(duì)象的getCause()方法將返回連接失敗的原因。

    4) 現(xiàn)在,連接操作結(jié)束,我們需要等待并且一直到這個(gè)Channel通道返回的closeFuture關(guān)閉這個(gè)連接。每一個(gè)Channel都可獲得自己的closeFuture對(duì)象,因此我們可以收到通知并在這個(gè)關(guān)閉時(shí)間點(diǎn)執(zhí)行某種操作。

    并且即使這個(gè)連接操作失敗,這個(gè)closeFuture仍舊會(huì)收到通知,因?yàn)檫@個(gè)代表連接的 Channel對(duì)象將會(huì)在連接操作失敗后自動(dòng)關(guān)閉。

    5) 在這個(gè)時(shí)間點(diǎn),所有的連接已被關(guān)閉。剩下的唯一工作是釋放ChannelFactory通道工廠使用的資源。這一步僅需要調(diào)用 releaseExternalResources()方法即可。包括NIO Secector和線程池在內(nèi)的所有資源將被自動(dòng)的關(guān)閉和終止。

    關(guān)閉一個(gè)客戶(hù)端應(yīng)用是很簡(jiǎn)單的,但又該如何關(guān)閉一個(gè)服務(wù)端應(yīng)用呢?你需要釋放其綁定的端口并關(guān)閉所有接受和打開(kāi)的連接。為了做到這一點(diǎn),你需要使用一種數(shù)據(jù)結(jié)構(gòu)記錄所有的活動(dòng)連接,但這卻并不是一件容易的事。幸運(yùn)的是,這里有一種解決方案,ChannelGroup。

    ChannelGroup是Java 集合 API的一個(gè)特有擴(kuò)展,ChannelGroup內(nèi)部持有所有打開(kāi)狀態(tài)的Channel通道。如果一個(gè)Channel通道對(duì)象被加入到 ChannelGroup,如果這個(gè)Channel通道被關(guān)閉,ChannelGroup將自動(dòng)移除這個(gè)關(guān)閉的Channel通道對(duì)象。此外,你還可以對(duì) 一個(gè)ChannelGroup對(duì)象內(nèi)部的所有Channel通道對(duì)象執(zhí)行相同的操作。例如,當(dāng)你關(guān)閉服務(wù)端應(yīng)用時(shí)你可以關(guān)閉一個(gè)ChannelGroup 內(nèi)部的所有Channel通道對(duì)象。

    為了記錄所有打開(kāi)的socket,你需要修改你的TimeServerHandler實(shí)現(xiàn),將一個(gè)打開(kāi)的Channel通道加入全局的ChannelGroup對(duì)象,TimeServer.allChannels:

    Java代碼
    @Override
    public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) {
    TimeServer.allChannels.add(e.getChannel());
    }
    代碼說(shuō)明
    是的,ChannelGroup是線程安全的。

    現(xiàn)在,所有活動(dòng)的Channel通道將被自動(dòng)的維護(hù),關(guān)閉一個(gè)服務(wù)端應(yīng)用有如關(guān)閉一個(gè)客戶(hù)端應(yīng)用一樣簡(jiǎn)單。

    Java代碼
    package org.jboss.netty.example.time;

    public class TimeServer {

    static final ChannelGroup allChannels = new DefaultChannelGroup("time-server" );

    public static void main(String[] args) throws Exception {
    ...
    ChannelFactory factory = ...;
    ServerBootstrap bootstrap = ...;
    ...
    Channel channel = bootstrap.bind(...);
    allChannels.add(channel);
    waitForShutdownCommand();
    ChannelGroupFuture future = allChannels.close();
    future.awaitUninterruptibly();
    factory.releaseExternalResources();
    }
    }
    代碼說(shuō)明
    1) DefaultChannelGroup需要一個(gè)組名作為其構(gòu)造器參數(shù)。這個(gè)組名僅是區(qū)分每個(gè)ChannelGroup的一個(gè)標(biāo)示。

    2) ServerBootstrap對(duì)象的bind方法返回了一個(gè)綁定了本地地址的服務(wù)端Channel通道對(duì)象。調(diào)用這個(gè)Channel通道的close()方法將釋放這個(gè)Channel通道綁定的本地地址。

    3) 不管這個(gè)Channel對(duì)象屬于服務(wù)端,客戶(hù)端,還是為響應(yīng)某一個(gè)請(qǐng)求創(chuàng)建,任何一種類(lèi)型的Channel對(duì)象都會(huì)被加入ChannelGroup。因此,你盡可在關(guān)閉服務(wù)時(shí)關(guān)閉所有的Channel對(duì)象。

    4) waitForShutdownCommand()是一個(gè)想象中等待關(guān)閉信號(hào)的方法。你可以在這里等待某個(gè)客戶(hù)端的關(guān)閉信號(hào)或者JVM的關(guān)閉回調(diào)命令。

    5) 你可以對(duì)ChannelGroup管理的所有Channel對(duì)象執(zhí)行相同的操作。在這個(gè)例子里,我們將關(guān)閉所有的通道,這意味著綁定在服務(wù)端特定地址的 Channel通道將解除綁定,所有已建立的連接也將異步關(guān)閉。為了獲得成功關(guān)閉所有連接的通知,close()方法將返回一個(gè) ChannelGroupFuture對(duì)象,這是一個(gè)類(lèi)似ChannelFuture的對(duì)象。

    1.10. 總述


    在這一章節(jié),我們快速瀏覽并示范了如何使用Netty開(kāi)發(fā)網(wǎng)絡(luò)應(yīng)用。下一章節(jié)將涉及更多的問(wèn)題。同時(shí)請(qǐng)記住,為了幫助你以及能夠讓Netty基于你的回饋得到持續(xù)的改進(jìn)和提高,Netty社區(qū) 將永遠(yuǎn)歡迎你的問(wèn)題及建議。



    第二章. 架構(gòu)總覽

    在這個(gè)章節(jié),我們將闡述Netty提供的核心功能以及在此基礎(chǔ)之上如何構(gòu)建一個(gè)完備的網(wǎng)絡(luò)應(yīng)用。

    2.1. 豐富的緩沖實(shí)現(xiàn)


    Netty使用自建的buffer API,而不是使用NIO的ByteBuffer來(lái)代表一個(gè)連續(xù)的字節(jié)序列。與ByteBuffer相比這種方式擁有明顯的優(yōu)勢(shì)。Netty使用新的 buffer類(lèi)型ChannelBuffer,ChannelBuffer被設(shè)計(jì)為一個(gè)可從底層解決ByteBuffer問(wèn)題,并可滿(mǎn)足日常網(wǎng)絡(luò)應(yīng)用開(kāi)發(fā) 需要的緩沖類(lèi)型。這些很酷的特性包括:



    如果需要,允許使用自定義的緩沖類(lèi)型。
    復(fù)合緩沖類(lèi)型中內(nèi)置的透明的零拷貝實(shí)現(xiàn)。
    開(kāi)箱即用的動(dòng)態(tài)緩沖類(lèi)型,具有像StringBuffer一樣的動(dòng)態(tài)緩沖能力。
    不再需要調(diào)用的flip()方法。
    正常情況下具有比ByteBuffer更快的響應(yīng)速度。
    更多信息請(qǐng)參考:org.jboss.netty.buffer package description

    2.2. 統(tǒng)一的異步 I/O API


    傳統(tǒng)的Java I/O API在應(yīng)對(duì)不同的傳輸協(xié)議時(shí)需要使用不同的類(lèi)型和方法。例如:java.net.Socket 和 java.net.DatagramSocket它們并不具有相同的超類(lèi)型,因此,這就需要使用不同的調(diào)用方式執(zhí)行socket操作。

    這種模式上的不匹配使得在更換一個(gè)網(wǎng)絡(luò)應(yīng)用的傳輸協(xié)議時(shí)變得繁雜和困難。由于(Java I/O API)缺乏協(xié)議間的移植性,當(dāng)你試圖在不修改網(wǎng)絡(luò)傳輸層的前提下增加多種協(xié)議的支持,這時(shí)便會(huì)產(chǎn)生問(wèn)題。并且理論上講,多種應(yīng)用層協(xié)議可運(yùn)行在多種傳輸 層協(xié)議之上例如TCP/IP,UDP/IP,SCTP和串口通信。

    讓這種情況變得更糟的是,Java新的I/O(NIO)API與原有的阻塞式的I/O(OIO)API并不兼容,NIO.2(AIO)也是如此。由于所有的API無(wú)論是在其設(shè)計(jì)上還是性能上的特性都與彼此不同,在進(jìn)入開(kāi)發(fā)階段,你常常會(huì)被迫的選擇一種你需要的API。

    例如,在用戶(hù)數(shù)較小的時(shí)候你可能會(huì)選擇使用傳統(tǒng)的OIO(Old I/O) API,畢竟與NIO相比使用OIO將更加容易一些。然而,當(dāng)你的業(yè)務(wù)呈指數(shù)增長(zhǎng)并且服務(wù)器需要同時(shí)處理成千上萬(wàn)的客戶(hù)連接時(shí)你便會(huì)遇到問(wèn)題。這種情況下 你可能會(huì)嘗試使用NIO,但是復(fù)雜的NIO Selector編程接口又會(huì)耗費(fèi)你大量時(shí)間并最終會(huì)阻礙你的快速開(kāi)發(fā)。

    Netty有一個(gè)叫做Channel的統(tǒng)一的異步I/O編程接口,這個(gè)編程接口抽象了所有點(diǎn)對(duì)點(diǎn)的通信操作。也就是說(shuō),如果你的應(yīng)用是基于Netty的某 一種傳輸實(shí)現(xiàn),那么同樣的,你的應(yīng)用也可以運(yùn)行在Netty的另一種傳輸實(shí)現(xiàn)上。Netty提供了幾種擁有相同編程接口的基本傳輸實(shí)現(xiàn):



    NIO-based TCP/IP transport (See org.jboss.netty.channel.socket.nio),
    OIO-based TCP/IP transport (See org.jboss.netty.channel.socket.oio),
    OIO-based UDP/IP transport, and
    Local transport (See org.jboss.netty.channel.local).
    切換不同的傳輸實(shí)現(xiàn)通常只需對(duì)代碼進(jìn)行幾行的修改調(diào)整,例如選擇一個(gè)不同的ChannelFactory實(shí)現(xiàn)。

    此外,你甚至可以利用新的傳輸實(shí)現(xiàn)沒(méi)有寫(xiě)入的優(yōu)勢(shì),只需替換一些構(gòu)造器的調(diào)用方法即可,例如串口通信。而且由于核心API具有高度的可擴(kuò)展性,你還可以完成自己的傳輸實(shí)現(xiàn)。

    2.3. 基于攔截鏈模式的事件模型


    一個(gè)定義良好并具有擴(kuò)展能力的事件模型是事件驅(qū)動(dòng)開(kāi)發(fā)的必要條件。Netty具有定義良好的I/O事件模型。由于嚴(yán)格的層次結(jié)構(gòu)區(qū)分了不同的事件類(lèi)型,因 此Netty也允許你在不破壞現(xiàn)有代碼的情況下實(shí)現(xiàn)自己的事件類(lèi)型。這是與其他框架相比另一個(gè)不同的地方。很多NIO框架沒(méi)有或者僅有有限的事件模型概 念;在你試圖添加一個(gè)新的事件類(lèi)型的時(shí)候常常需要修改已有的代碼,或者根本就不允許你進(jìn)行這種擴(kuò)展。

    在一個(gè)ChannelPipeline內(nèi)部一個(gè)ChannelEvent被一組ChannelHandler處理。這個(gè)管道是攔截過(guò)濾器 模式的一種高級(jí)形式的實(shí)現(xiàn),因此對(duì)于一個(gè)事件如何被處理以及管道內(nèi)部處理器間的交互過(guò)程,你都將擁有絕對(duì)的控制力。例如,你可以定義一個(gè)從socket讀取到數(shù)據(jù)后的操作:

    Java代碼
    public class MyReadHandler implements SimpleChannelHandler {
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent evt) {
    Object message = evt.getMessage();
    // Do something with the received message.
    ...

    // And forward the event to the next handler.
    ctx.sendUpstream(evt);
    }
    }

    同時(shí)你也可以定義一種操作響應(yīng)其他處理器的寫(xiě)操作請(qǐng)求:

    Java代碼
    public class MyWriteHandler implements SimpleChannelHandler {
    public void writeRequested(ChannelHandlerContext ctx, MessageEvent evt) {
    Object message = evt.getMessage();
    // Do something with the message to be written.
    ...

    // And forward the event to the next handler.
    ctx.sendDownstream(evt);
    }
    }

    有關(guān)事件模型的更多信息,請(qǐng)參考API文檔ChannelEvent和ChannelPipeline部分。

    2.4. 適用快速開(kāi)發(fā)的高級(jí)組件


    上述所提及的核心組件已經(jīng)足夠?qū)崿F(xiàn)各種類(lèi)型的網(wǎng)絡(luò)應(yīng)用,除此之外,Netty也提供了一系列的高級(jí)組件來(lái)加速你的開(kāi)發(fā)過(guò)程。

    2.4.1. Codec框架


    就像“1.8. 使用POJO代替ChannelBuffer”一節(jié)所展示的那樣,從業(yè)務(wù)邏輯代碼中分離協(xié)議處理部分總是一個(gè)很不錯(cuò)的想法。然而如果一切從零開(kāi)始便會(huì)遭遇 到實(shí)現(xiàn)上的復(fù)雜性。你不得不處理分段的消息。一些協(xié)議是多層的(例如構(gòu)建在其他低層協(xié)議之上的協(xié)議)。一些協(xié)議過(guò)于復(fù)雜以致難以在一臺(tái)主機(jī)(single state machine)上實(shí)現(xiàn)。

    因此,一個(gè)好的網(wǎng)絡(luò)應(yīng)用框架應(yīng)該提供一種可擴(kuò)展,可重用,可單元測(cè)試并且是多層的codec框架,為用戶(hù)提供易維護(hù)的codec代碼。

    Netty提供了一組構(gòu)建在其核心模塊之上的codec實(shí)現(xiàn),這些簡(jiǎn)單的或者高級(jí)的codec實(shí)現(xiàn)幫你解決了大部分在你進(jìn)行協(xié)議處理開(kāi)發(fā)過(guò)程會(huì)遇到的問(wèn)題,無(wú)論這些協(xié)議是簡(jiǎn)單的還是復(fù)雜的,二進(jìn)制的或是簡(jiǎn)單文本的。

    2.4.2. SSL / TLS 支持


    不同于傳統(tǒng)阻塞式的I/O實(shí)現(xiàn),在NIO模式下支持SSL功能是一個(gè)艱難的工作。你不能只是簡(jiǎn)單的包裝一下流數(shù)據(jù)并進(jìn)行加密或解密工作,你不得不借助于 javax.net.ssl.SSLEngine,SSLEngine是一個(gè)有狀態(tài)的實(shí)現(xiàn),其復(fù)雜性不亞于SSL自身。你必須管理所有可能的狀態(tài),例如密 碼套件,密鑰協(xié)商(或重新協(xié)商),證書(shū)交換以及認(rèn)證等。此外,與通常期望情況相反的是SSLEngine甚至不是一個(gè)絕對(duì)的線程安全實(shí)現(xiàn)。

    在Netty內(nèi)部,SslHandler封裝了所有艱難的細(xì)節(jié)以及使用SSLEngine可能帶來(lái)的陷阱。你所做的僅是配置并將該SslHandler插入到你的ChannelPipeline中。同樣Netty也允許你實(shí)現(xiàn)像StartTlS 那樣所擁有的高級(jí)特性,這很容易。

    2.4.3. HTTP實(shí)現(xiàn)


    HTTP無(wú)疑是互聯(lián)網(wǎng)上最受歡迎的協(xié)議,并且已經(jīng)有了一些例如Servlet容器這樣的HTTP實(shí)現(xiàn)。因此,為什么Netty還要在其核心模塊之上構(gòu)建一套HTTP實(shí)現(xiàn)?

    與現(xiàn)有的HTTP實(shí)現(xiàn)相比Netty的HTTP實(shí)現(xiàn)是相當(dāng)與眾不同的。在HTTP消息的低層交互過(guò)程中你將擁有絕對(duì)的控制力。這是因?yàn)镹etty的 HTTP實(shí)現(xiàn)只是一些HTTP codec和HTTP消息類(lèi)的簡(jiǎn)單組合,這里不存在任何限制——例如那種被迫選擇的線程模型。你可以隨心所欲的編寫(xiě)那種可以完全按照你期望的工作方式工作 的客戶(hù)端或服務(wù)器端代碼。這包括線程模型,連接生命期,快編碼,以及所有HTTP協(xié)議允許你做的,所有的一切,你都將擁有絕對(duì)的控制力。

    由于這種高度可定制化的特性,你可以開(kāi)發(fā)一個(gè)非常高效的HTTP服務(wù)器,例如:

    要求持久化鏈接以及服務(wù)器端推送技術(shù)的聊天服務(wù)(e.g. Comet )
    需要保持鏈接直至整個(gè)文件下載完成的媒體流服務(wù)(e.g. 2小時(shí)長(zhǎng)的電影)
    需要上傳大文件并且沒(méi)有內(nèi)存壓力的文件服務(wù)(e.g. 上傳1GB文件的請(qǐng)求)
    支持大規(guī)模mash-up應(yīng)用以及數(shù)以萬(wàn)計(jì)連接的第三方web services異步處理平臺(tái)
    2.4.4. Google Protocol Buffer 整合


    Google Protocol Buffers 是快速實(shí)現(xiàn)一個(gè)高效的二進(jìn)制協(xié)議的理想方案。通過(guò)使用ProtobufEncoder和ProtobufDecoder,你可以把Google Protocol Buffers 編譯器 (protoc)生成的消息類(lèi)放入到Netty的codec實(shí)現(xiàn)中。請(qǐng)參考“LocalTime ”實(shí)例,這個(gè)例子也同時(shí)顯示出開(kāi)發(fā)一個(gè)由簡(jiǎn)單協(xié)議定義 的客戶(hù)及服務(wù)端是多么的容易。

    2.5. 總述
    在這一章節(jié),我們從功能特性的角度回顧了Netty的整體架構(gòu)。Netty有一個(gè)簡(jiǎn)單卻不失強(qiáng)大的架構(gòu)。這個(gè)架構(gòu)由三部分組成——緩沖(buffer), 通道(channel),事件模型(event model)——所有的高級(jí)特性都構(gòu)建在這三個(gè)核心組件之上。一旦你理解了它們之間的工作原理,你便不難理解在本章簡(jiǎn)要提及的更多高級(jí)特性。

    你可能對(duì)Netty的整體架構(gòu)以及每一部分的工作原理仍舊存有疑問(wèn)。如果是這樣,最好的方式是告訴我們 應(yīng)該如何改進(jìn)這份指南
    posted @ 2013-06-07 16:11 不高興 閱讀(735) | 評(píng)論 (4)編輯 收藏
     
    bug描述:
        parseInt("08")=0;
        parseInt("09")=0;

    這里先回顧一下parseInt的函數(shù)聲明:

    /*
     * 將字符串解析成數(shù)字時(shí),從左向右依此解析,解析到第一個(gè)非法字符即停止。
     * 若指定radix為2-36之間的數(shù)字,則按相應(yīng)的進(jìn)制進(jìn)行解析;
     * 若radix指定為1,或大于36的數(shù)字,則直接返回NaN
     * 若指定radix為0,或未指定radix,則根據(jù)字符串開(kāi)頭字符確定:
     *  以'1-9'開(kāi)頭的字符串,按10進(jìn)制解析;
     *  以'0'開(kāi)頭的字符串,按8進(jìn)制解析;
     *  以'0x'或'0X'開(kāi)頭的字符串,按16進(jìn)制解析。
     * 
     * @param string 要被解析的字符串。
     * @param radix  表示要解析的數(shù)字的基數(shù)。該值介于 2 ~ 36 之間。
     
    */
    parseInt(string, radix)


     ‘01’到’07’,按8進(jìn)制或10進(jìn)制解析會(huì)得到相同的結(jié)果。 而’08’,’09’按8進(jìn)制解析會(huì)得到’0’,因?yàn)?#8217;8’、’9’在8進(jìn)制中是非法字符,不會(huì)被解析。由此導(dǎo)致上述的bug。
    找到問(wèn)題根源,修復(fù)就變得很簡(jiǎn)單了,顯示指定radix為10.
    parseInt("08",10);
    posted @ 2012-11-08 13:49 不高興 閱讀(565) | 評(píng)論 (4)編輯 收藏
     

    在本教程中,我們將看到使用Eclipse調(diào)試Java應(yīng)用程序。調(diào)試可以幫助我們識(shí)別和解決應(yīng)用程序中的缺陷。我們將重點(diǎn)放在運(yùn)行時(shí)間的問(wèn)題,而不是編譯時(shí)錯(cuò)誤。有提供像gdb的命令行調(diào)試器。在本教程中,我們將集中在基于GUI的調(diào)試,我們把我們最喜愛(ài)的IDE Eclipse來(lái)運(yùn)行,通過(guò)本教程。雖然我們說(shuō)的Eclipse,點(diǎn)大多是通用的,適用于調(diào)試使用的IDE像NetBeans。

    在看這篇文章前,我推薦你看一下Eclipse 快捷鍵手冊(cè),你也可以到這兒:下載PDF文件我的eclipse版本是4.2 Juno。

    0.三點(diǎn)特別提醒:

    • 不要使用System.out.println作為調(diào)試工具
    • 啟用所有組件的詳細(xì)的日志記錄級(jí)別
    • 使用一個(gè)日志分析器來(lái)閱讀日志

    [

    (System.out.println()對(duì)開(kāi)發(fā)人員來(lái)說(shuō),有時(shí)候也許可以是一種調(diào)試手段,但是項(xiàng)目一旦完成他就沒(méi)有什么用途了,就變成垃圾了,得必須注釋或刪除掉,這樣會(huì)比較麻煩。啟用所有組件的詳細(xì)日志記錄級(jí)別,運(yùn)用日志分析器來(lái)記錄詳細(xì)系統(tǒng)運(yùn)行狀態(tài),這對(duì)后期網(wǎng)站的優(yōu)化和維護(hù)會(huì)有很多作用。)這僅僅是個(gè)人理解,僅供參考!

    ]


    1.條件斷點(diǎn)

    想象一下我們平時(shí)如何添加斷點(diǎn),通常的做法是雙擊行號(hào)的左邊。在debug視圖中,BreakPoint View將所有斷點(diǎn)都列出來(lái),但是我們可以添加一個(gè)boolean類(lèi)型的條件來(lái)決定斷點(diǎn)是否被跳過(guò)。如果條件為真,在斷點(diǎn)處程序?qū)⑼V梗駝t斷點(diǎn)被跳過(guò),程序繼續(xù)執(zhí)行。

    2.異常斷點(diǎn)

    在斷點(diǎn)view中有一個(gè)看起來(lái)像J!的按鈕,我們可以使用它添加一個(gè)基于異常的斷點(diǎn),例如我們希望當(dāng)NullPointerException拋出的時(shí)候程序暫停,我們可以這樣:

    3.觀察點(diǎn)

    這是一個(gè)很好的功能,他允許當(dāng)一個(gè)選定的屬性被訪問(wèn)或者被更改的時(shí)候程序執(zhí)行暫停,并進(jìn)行debug。最簡(jiǎn)單的辦法是在類(lèi)中聲明成員變量的語(yǔ)句行號(hào)左邊雙擊,就可以加入一個(gè)觀察點(diǎn)。

    4.查看變量

    在選中的變量上使用Ctrl+Shift+d 或者 Ctrl+Shift+i可以查看變量值,另外我們還可以在Expressions View中添加監(jiān)視。

    5.更改變量的值

    我們可以在Debug的時(shí)候改變其中變量的值。在Variables View中可以按下圖所示操作。

    6.在主方法停止

    在Run/Debug設(shè)置中,我們可以按如下圖所示的啟用這個(gè)特性。程序?qū)?huì)在main方法的第一行停住

    7.環(huán)境變量

    我們可以很方便的在Edit Conriguration對(duì)話框中添加環(huán)境變量

    8.跳出函數(shù)到選定層

    這個(gè)功能非???,是我第二個(gè)非常喜歡的功能,Drop to frame就是說(shuō),可以重新跳到當(dāng)前方法的開(kāi)始處重新執(zhí)行,并且所有上下文變量的值也回到那個(gè)時(shí)候。不一定是當(dāng)前方法,可以點(diǎn)擊當(dāng)前調(diào)用棧中的任何一個(gè)frame跳到那里(除了最開(kāi)始的那個(gè)frame)。主要用途是所有變量狀態(tài)快速恢復(fù)到方法開(kāi)始時(shí)候的樣子重新執(zhí)行一遍,即可以一遍又一遍地在那個(gè)你關(guān)注的上下文中進(jìn)行多次調(diào)試(結(jié)合改變變量值等其它功能),而不用重來(lái)一遍調(diào)試到哪里了。當(dāng)然,原來(lái)執(zhí)行過(guò)程中產(chǎn)生的副作用是不可逆的(比如你往數(shù)據(jù)庫(kù)中插入了一條記錄)。

    9.分步過(guò)濾

    當(dāng)我們?cè)谡{(diào)試的時(shí)候摁F5將進(jìn)入方法的內(nèi)部,但這有個(gè)缺點(diǎn)有的時(shí)候可能會(huì)進(jìn)入到一些庫(kù)的內(nèi)部(例如JDK),可能并不是我們想要的,我們可以在Preferences中添加一個(gè)過(guò)濾器,排除指定的包。

    10.跳入,跳過(guò)和返回

    其實(shí)這個(gè)技巧是debug最基本的知識(shí)。
    • F5-Step Into:移動(dòng)到下一步,如果當(dāng)前的行是一個(gè)方法調(diào)用,將進(jìn)入這個(gè)方法的第一行。(可以通過(guò)第九條來(lái)排除)
    • F6-Step Over:移動(dòng)到下一行。如果當(dāng)前行有方法調(diào)用,這個(gè)方法將被執(zhí)行完畢返回,然后到下一行。
    • F7-Step Return:繼續(xù)執(zhí)行當(dāng)前方法,當(dāng)當(dāng)前方法執(zhí)行完畢的時(shí)候,控制將轉(zhuǎn)到當(dāng)前方法被調(diào)用的行。
    • F8-移動(dòng)到下一個(gè)斷點(diǎn)處。


    posted @ 2012-10-19 16:54 不高興 閱讀(705) | 評(píng)論 (5)編輯 收藏
     
    <a href="itms-services://?action=download-manifest&amp;url=http://test1.gc73.com.cn/hoho.plist"> 

    http://test1.gc73.com.cn/hoho.plist 文件格式如下
    <?xml version="1.0" encoding="gbk"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> 	<key>items</key> 	<array> 		<dict> 			<key>assets</key> 			<array> 				<dict> 					<key>kind</key> 					<string>software-package</string> 					<key>url</key> 					<string>http://dlx1.gc73.com/pokerddzV2.1-7.20.iPad.ipa</string> 				</dict> 				<dict> 					<key>kind</key> 					<string>full-size-image</string> 					<key>needs-shine</key> 					<false/> 					<key>url</key> 					<string>http://test1.gc73.com.cn/ipad_tmp.png</string> 				</dict> 				<dict> 					<key>kind</key> 					<string>display-image</string> 					<key>needs-shine</key> 					<false/> 					<key>url</key> 					<string>http://test1.gc73.com.cn/ipad_tmp.png</string> 				</dict> 			</array> 			<key>metadata</key> 			<dict> 				<key>bundle-identifier</key> 				<string>com.pokercity.fightlordiPad</string> 				<key>kind</key> 				<string>software</string> 				<key>subtitle</key> 				<string>咱的測(cè)試</string> 				<key>title</key> 				<string>咱的測(cè)試(越獄版)</string> 			</dict> 		</dict> 	</array> </dict> </plist>
    posted @ 2012-07-26 10:35 不高興 閱讀(1463) | 評(píng)論 (6)編輯 收藏
     
    用tortoisehg下載google code時(shí)報(bào)錯(cuò)abort:error
    解決方案:將https: 換成http 試一試
    posted @ 2012-05-30 22:25 不高興 閱讀(899) | 評(píng)論 (4)編輯 收藏
     
        VisualVM是Sun的一個(gè)OpenJDK項(xiàng)目,其目的在于為Java應(yīng)用創(chuàng)建一個(gè)整套的問(wèn)題解決工具。它集成了多個(gè)JDK命令工具的一個(gè)可視化工具,它主要用來(lái)監(jiān)控JVM的運(yùn)行情況,可以用它來(lái)查看和瀏覽Heap Dump、Thread Dump、內(nèi)存對(duì)象實(shí)例情況、GC執(zhí)行情況、CPU消耗以及類(lèi)的裝載情況。 Java開(kāi)發(fā)人員可以使用 VisualVM創(chuàng)建必要信息的日志,系統(tǒng)管理人員可用來(lái)監(jiān)控及控制Java應(yīng)用程序在網(wǎng)絡(luò)中的運(yùn)行狀況。 
       下載頁(yè)面 : https://visualvm.dev.java.net/download.html

        文檔地址 : https://visualvm.dev.java.net/docindex.html

        入門(mén)文檔 : https://visualvm.dev.java.net/zh_CN/gettingstarted.html

        


        安裝插件
        通過(guò)安裝 VisualVM 更新中心提供的插件,可以向 VisualVM 添加功能。
        1. 從主菜單中選擇“工具”>“插件”。
        2. 在“可用插件”標(biāo)簽中,選中該插件的“安裝”復(fù)選框。單擊“安裝”。
        3. 逐步完成插件安裝程序。
        


        功能    
        1. 概述
            查看jvm信息及系統(tǒng)配置
        

        2. 監(jiān)視
            了解項(xiàng)目運(yùn)動(dòng)的概況
        

        3. visual gc
            可以看到內(nèi)存gc的詳細(xì)情況
            


        遠(yuǎn)程監(jiān)控    
        1. 通過(guò)jstatd啟動(dòng)RMI服務(wù)
            配置java安全訪問(wèn),將如下的代碼存為文件 jstatd.all.policy,放到JAVA_HOME/bin中,其內(nèi)容如下,
            grant codebase "file:${java.home}/../lib/tools.jar" {

                   permission java.security.AllPermission;

              };
                
              執(zhí)行命令jstatd -J-Djava.security.policy=jstatd.all.policy -J-Djava.rmi.server.hostname=192.168.1.8 &(192.168.1.8  為你服務(wù)器的ip地址,&表示用守護(hù)線程的方式運(yùn)行)
              jstatd命令詳解 :http://hzl7652.iteye.com/blog/1183182 
             
              打開(kāi)jvisualvm, 右鍵Remort,選擇 "Add Remort Host...",在彈出框中輸入你的遠(yuǎn)端IP,比如192.168.1.8. 連接成功.
        
        2. 配置JMX管理tomcat
            打開(kāi)Tomcat的bin/catalina.bat,如果為linux或unix系統(tǒng),則為catalina.sh文件 。 
               無(wú)限制訪問(wèn) 
               

      set JAVA_OPTS=-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9008 -Dcom.sun.management.jmxremote.authenticate=false -    Dcom.sun.management.jmxremote.ssl=false 

    需要用戶(hù)名和密碼訪問(wèn)
            JAVA_OPTS='-Xms128m -Xmx256m -XX:MaxPermSize=128m
            -Djava.rmi.server.hostname=192.168.1.8
            -Dcom.sun.management.jmxremote.port=8088
            -Dcom.sun.management.jmxremote.ssl=false
            -Dcom.sun.management.jmxremote.authenticate=true
            -Dcom.sun.management.jmxremote.password.file=/usr/java/default/jre/lib/management/jmxremote.password
            -Dcom.sun.management.jmxremote.access.file=/usr/java/default/jre/lib/management/jmxremote.access'
              (jmxremote.access 在JAVA_HOME\jre\lib\management下有模板)
             jmxremote.access 中顯示
             monitorRole   readonly
             controlRole   readwrite
            
            jmxremote.password中顯示
            monitorRole  QED            (QED為密碼)
            controlRole   R&D

            重新在visualvm中打開(kāi)遠(yuǎn)程tomcat就可以使用JMX帶來(lái)的功能了

     

    posted @ 2012-03-20 21:59 不高興 閱讀(30236) | 評(píng)論 (2)編輯 收藏
     
    Ctrl+1 快速修復(fù)(最經(jīng)典的快捷鍵,就不用多說(shuō)了)
    Ctrl+D: 刪除當(dāng)前行 
    Ctrl+Alt+↓ 復(fù)制當(dāng)前行到下一行(復(fù)制增加)
    Ctrl+Alt+↑ 復(fù)制當(dāng)前行到上一行(復(fù)制增加)
    Alt+↓ 當(dāng)前行和下面一行交互位置(特別實(shí)用,可以省去先剪切,再粘貼了)
    Alt+↑ 當(dāng)前行和上面一行交互位置(同上)
    Alt+← 前一個(gè)編輯的頁(yè)面
    Alt+→ 下一個(gè)編輯的頁(yè)面(當(dāng)然是針對(duì)上面那條來(lái)說(shuō)了)
    Alt+Enter 顯示當(dāng)前選擇資源(工程,or 文件 or文件)的屬性
    Shift+Enter 在當(dāng)前行的下一行插入空行(這時(shí)鼠標(biāo)可以在當(dāng)前行的任一位置,不一定是最后)
    Shift+Ctrl+Enter 在當(dāng)前行插入空行(原理同上條)
    Ctrl+Q 定位到最后編輯的地方
    Ctrl+L 定位在某行 (對(duì)于程序超過(guò)100的人就有福音了)
    Ctrl+M 最大化當(dāng)前的Edit或View (再按則反之)
    Ctrl+/ 注釋當(dāng)前行,再按則取消注釋
    Ctrl+O 快速顯示 OutLine
    Ctrl+T 快速顯示當(dāng)前類(lèi)的繼承結(jié)構(gòu)
    Ctrl+W 關(guān)閉當(dāng)前Editer
    Ctrl+K 參照選中的Word快速定位到下一個(gè)
    Ctrl+E 快速顯示當(dāng)前Editer的下拉列表(如果當(dāng)前頁(yè)面沒(méi)有顯示的用黑體表示)
    Ctrl+/(小鍵盤(pán)) 折疊當(dāng)前類(lèi)中的所有代碼
    Ctrl+×(小鍵盤(pán)) 展開(kāi)當(dāng)前類(lèi)中的所有代碼
    Ctrl+Space 代碼助手完成一些代碼的插入(但一般和輸入法有沖突,可以修改輸入法的熱鍵,也可以暫用Alt+/來(lái)代替)
    Ctrl+Shift+E 顯示管理當(dāng)前打開(kāi)的所有的View的管理器(可以選擇關(guān)閉,激活等操作)
    Ctrl+J 正向增量查找(按下Ctrl+J后,你所輸入的每個(gè)字母編輯器都提供快速匹配定位到某個(gè)單詞,如果沒(méi)有,則在stutes line中顯示沒(méi)有找到了,查一個(gè)單詞時(shí),特別實(shí)用,這個(gè)功能Idea兩年前就有了)
    Ctrl+Shift+J 反向增量查找(和上條相同,只不過(guò)是從后往前查)
    Ctrl+Shift+F4 關(guān)閉所有打開(kāi)的Editer
    Ctrl+Shift+X 把當(dāng)前選中的文本全部變味小寫(xiě)
    Ctrl+Shift+Y 把當(dāng)前選中的文本全部變?yōu)樾?xiě)
    Ctrl+Shift+F 格式化當(dāng)前代碼
    Ctrl+Shift+P 定位到對(duì)于的匹配符(譬如{}) (從前面定位后面時(shí),光標(biāo)要在匹配符里面,后面到前面,則反之)

    下面的快捷鍵是重構(gòu)里面常用的,本人就自己喜歡且常用的整理一下(注:一般重構(gòu)的快捷鍵都是Alt+Shift開(kāi)頭的了)
    Alt+Shift+R 重命名 (是我自己最?lèi)?ài)用的一個(gè)了,尤其是變量和類(lèi)的Rename,比手工方法能節(jié)省很多勞動(dòng)力)
    Alt+Shift+M 抽取方法 (這是重構(gòu)里面最常用的方法之一了,尤其是對(duì)一大堆泥團(tuán)代碼有用)
    Alt+Shift+C 修改函數(shù)結(jié)構(gòu)(比較實(shí)用,有N個(gè)函數(shù)調(diào)用了這個(gè)方法,修改一次搞定)
    Alt+Shift+L 抽取本地變量( 可以直接把一些魔法數(shù)字和字符串抽取成一個(gè)變量,尤其是多處調(diào)用的時(shí)候)
    Alt+Shift+F 把Class中的local變量變?yōu)閒ield變量 (比較實(shí)用的功能)
    Alt+Shift+I 合并變量(可能這樣說(shuō)有點(diǎn)不妥Inline)
    Alt+Shift+V 移動(dòng)函數(shù)和變量(不怎么常用)
    Alt+Shift+Z 重構(gòu)的后悔藥(Undo)

    編輯
    作用域 功能 快捷鍵 
    全局 查找并替換 Ctrl+F 
    文本編輯器 查找上一個(gè) Ctrl+Shift+K 
    文本編輯器 查找下一個(gè) Ctrl+K 
    全局 撤銷(xiāo) Ctrl+Z 
    全局 復(fù)制 Ctrl+C 
    全局 恢復(fù)上一個(gè)選擇 Alt+Shift+↓ 
    全局 剪切 Ctrl+X 
    全局 快速修正 Ctrl1+1 
    全局 內(nèi)容輔助 Alt+/ 
    全局 全部選中 Ctrl+A 
    全局 刪除 Delete 
    全局 上下文信息 Alt+?
    Alt+Shift+?
    Ctrl+Shift+Space 
    Java編輯器 顯示工具提示描述 F2 
    Java編輯器 選擇封裝元素 Alt+Shift+↑ 
    Java編輯器 選擇上一個(gè)元素 Alt+Shift+← 
    Java編輯器 選擇下一個(gè)元素 Alt+Shift+→ 
    文本編輯器 增量查找 Ctrl+J 
    文本編輯器 增量逆向查找 Ctrl+Shift+J 
    全局 粘貼 Ctrl+V 
    全局 重做 Ctrl+Y 

     
    查看
    作用域 功能 快捷鍵 
    全局 放大 Ctrl+= 
    全局 縮小 Ctrl+- 

     
    窗口
    作用域 功能 快捷鍵 
    全局 激活編輯器 F12 
    全局 切換編輯器 Ctrl+Shift+W 
    全局 上一個(gè)編輯器 Ctrl+Shift+F6 
    全局 上一個(gè)視圖 Ctrl+Shift+F7 
    全局 上一個(gè)透視圖 Ctrl+Shift+F8 
    全局 下一個(gè)編輯器 Ctrl+F6 
    全局 下一個(gè)視圖 Ctrl+F7 
    全局 下一個(gè)透視圖 Ctrl+F8 
    文本編輯器 顯示標(biāo)尺上下文菜單 Ctrl+W 
    全局 顯示視圖菜單 Ctrl+F10 
    全局 顯示系統(tǒng)菜單 Alt+- 

     
    導(dǎo)航
    作用域 功能 快捷鍵 
    Java編輯器 打開(kāi)結(jié)構(gòu) Ctrl+F3 
    全局 打開(kāi)類(lèi)型 Ctrl+Shift+T 
    全局 打開(kāi)類(lèi)型層次結(jié)構(gòu) F4 
    全局 打開(kāi)聲明 F3 
    全局 打開(kāi)外部javadoc Shift+F2 
    全局 打開(kāi)資源 Ctrl+Shift+R 
    全局 后退歷史記錄 Alt+← 
    全局 前進(jìn)歷史記錄 Alt+→ 
    全局 上一個(gè) Ctrl+, 
    全局 下一個(gè) Ctrl+. 
    Java編輯器 顯示大綱 Ctrl+O 
    全局 在層次結(jié)構(gòu)中打開(kāi)類(lèi)型 Ctrl+Shift+H 
    全局 轉(zhuǎn)至匹配的括號(hào) Ctrl+Shift+P 
    全局 轉(zhuǎn)至上一個(gè)編輯位置 Ctrl+Q 
    Java編輯器 轉(zhuǎn)至上一個(gè)成員 Ctrl+Shift+↑ 
    Java編輯器 轉(zhuǎn)至下一個(gè)成員 Ctrl+Shift+↓ 
    文本編輯器 轉(zhuǎn)至行 Ctrl+L 

     
    搜索
    作用域 功能 快捷鍵 
    全局 出現(xiàn)在文件中 Ctrl+Shift+U 
    全局 打開(kāi)搜索對(duì)話框 Ctrl+H 
    全局 工作區(qū)中的聲明 Ctrl+G 
    全局 工作區(qū)中的引用 Ctrl+Shift+G 

     
    文本編輯
    作用域 功能 快捷鍵 
    文本編輯器 改寫(xiě)切換 Insert 
    文本編輯器 上滾行 Ctrl+↑ 
    文本編輯器 下滾行 Ctrl+↓ 

     
    文件
    作用域 功能 快捷鍵 
    全局 保存 Ctrl+X 
    Ctrl+S 
    全局 打印 Ctrl+P 
    全局 關(guān)閉 Ctrl+F4 
    全局 全部保存 Ctrl+Shift+S 
    全局 全部關(guān)閉 Ctrl+Shift+F4 
    全局 屬性 Alt+Enter 
    全局 新建 Ctrl+N 

     
    項(xiàng)目
    作用域 功能 快捷鍵 
    全局 全部構(gòu)建 Ctrl+B 

     
    源代碼
    作用域 功能 快捷鍵 
    Java編輯器 格式化 Ctrl+Shift+F 
    Java編輯器 取消注釋 Ctrl+\ 
    Java編輯器 注釋 Ctrl+/ 
    Java編輯器 添加導(dǎo)入 Ctrl+Shift+M 
    Java編輯器 組織導(dǎo)入 Ctrl+Shift+O 
    Java編輯器 使用try/catch塊來(lái)包圍 未設(shè)置,太常用了,所以在這里列出,建議自己設(shè)置。
    也可以使用Ctrl+1自動(dòng)修正。 

     
    運(yùn)行
    作用域 功能 快捷鍵 
    全局 單步返回 F7 
    全局 單步跳過(guò) F6 
    全局 單步跳入 F5 
    全局 單步跳入選擇 Ctrl+F5 
    全局 調(diào)試上次啟動(dòng) F11 
    全局 繼續(xù) F8 
    全局 使用過(guò)濾器單步執(zhí)行 Shift+F5 
    全局 添加/去除斷點(diǎn) Ctrl+Shift+B 
    全局 顯示 Ctrl+D 
    全局 運(yùn)行上次啟動(dòng) Ctrl+F11 
    全局 運(yùn)行至行 Ctrl+R 
    全局 執(zhí)行 Ctrl+U 

     
    重構(gòu)
    作用域 功能 快捷鍵 
    全局 撤銷(xiāo)重構(gòu) Alt+Shift+Z 
    全局 抽取方法 Alt+Shift+M 
    全局 抽取局部變量 Alt+Shift+L 
    全局 內(nèi)聯(lián) Alt+Shift+I 
    全局 移動(dòng) Alt+Shift+V 
    全局 重命名 Alt+Shift+R 
    全局 重做 Alt+Shift+Y
    posted @ 2012-03-07 15:52 不高興 閱讀(592) | 評(píng)論 (3)編輯 收藏
     
        以字母或下劃線開(kāi)頭!
        今天找一個(gè)css的加載問(wèn)題,class已經(jīng)被瀏覽器加載到,但是屬性值一直不顯示出來(lái),幾經(jīng)查找才發(fā)現(xiàn)className以數(shù)字開(kāi)頭命名的,導(dǎo)致瀏覽器不認(rèn)可,整一個(gè)悲劇
    posted @ 2012-02-03 17:58 不高興 閱讀(1062) | 評(píng)論 (2)編輯 收藏
     
    BTrace的技術(shù)分析,本人暫沒(méi)有這個(gè)技術(shù)能力,大家可以看 http://www.iteye.com/topic/1005918,
    http://mgoann.iteye.com/blog/1409667 
    下面是個(gè)人寫(xiě)的一些簡(jiǎn)單實(shí)例,不過(guò)我一直沒(méi)辦法通過(guò)BTrace拿到局部變量的值,不知道哪位牛人幫幫解答下
    Linux下:
    在http://kenai.com/projects/btrace下載btrace-bin.tar.gz,并解壓,在/etc/profile設(shè)置環(huán)境變量: 
    1. export BTRACE_HOME=/home/workspace/btrace/
    2. export PATH=$BTRACE_HOME/bin:$PATH  
    設(shè)置完成后 source /etc/profile
    給執(zhí)行文件賦權(quán)限 chmod +x btrace

    /* BTrace Script Template */
    import com.sun.btrace.annotations.*;
    import static com.sun.btrace.BTraceUtils.*;
    import java.lang.reflect.Field;
    @BTrace
    public class TracingScript {
    /* put your code here */
    //打印實(shí)例屬性
    @OnMethod(clazz = "com.gameplus.action.siteLobby.LobbyAction", method = "/.*bankPage/", location = @Location(value = Kind.ENTRY))
    //clazz = "com.gameplus.action.siteLobby.LobbyAction" 表示監(jiān)控的類(lèi),method = "/.*bankPage/"表示監(jiān)控的方法 ,這兩個(gè)參數(shù)都可以用正則匹配
    //如果是接口用+號(hào)clazz = "+com.gameplus.action.siteLobby.LobbyAction" 
    public static void bufferMonitor(@Self Object self ){ // @Self 表示監(jiān)控點(diǎn)實(shí)例 
       print(strcat(strcat(name(probeClass()), "."), probeMethod())); //probeClass()監(jiān)控的類(lèi),probeMethod()監(jiān)控的方法
            println(self);
            println(get(field(classOf(self), "money"))); //只能取值static變量
    println(get(field(classOf(self), "money"),self)); //可以取值當(dāng)前實(shí)例變量,static也可以取到
    Field moneyField = field("com.gameplus.action.siteLobby.LobbyAction", "money"); //知道class的名稱(chēng)也可以取值
    get(moneyField,self);
    get(field("com.gameplus.action.siteLobby.LobbyAction", "money"), self); 
            Object montmp =get(field(getSuperclass(classOf(self)), "user"), self); //獲取父類(lèi)變量的方法
            println(str(montmp));
    long userId = (Long)get(field(classOf(montmp),"userId"),montmp); //獲取superClass.Object.變量值
            println(userId);
        }
    //打印運(yùn)行時(shí)lineNumber
        @OnMethod(clazz = "com.gameplus.action.siteLobby.LobbyAction", location=@Location(value=Kind.LINE, line=-1))
        public static void online(@ProbeClassName String pcn, @ProbeMethodName String pmn, int line) {
    print(Strings.strcat(pcn, ".")); //className
    print(Strings.strcat(pmn, ":")); //methodName
    println(line); //lineNumber
    //結(jié)果為:com.gameplus.action.siteLobby.LobbyAction.bankPage:161
    }
    //打印傳遞的參數(shù)值
    import com.sun.btrace.AnyType;
    @OnMethod(clazz="com.gameplus.service.operateBankService.OperateBankService",method="/.*/")
    public static void anyRead(@ProbeClassName String pcn, @ProbeMethodName String pmn, AnyType[] args) {
    println(pcn);
    println(pmn);
    printArray(args);
    }
    //打印所有屬性
    @OnMethod(clazz = "com.gameplus.action.siteLobby.LobbyAction", method = "/.*bankPage/", location = @Location(value = Kind.RETURN))
        public static void bufferMonitor(@Self Object self,@Return Object command ,@Duration long time){ 
            printFields(self);
            Object montmp =get(field(getSuperclass(classOf(self)), "user"), self);
            printFields(montmp);
    //{password=null, newPassword=null, rePassword=, bankPassword=, newBankPassword=, reBankPassword=, operateType=0, operateBankType=0, money=111, integral=0, dateStartQuery=, dateEndQuery=, isDefaultPasswd=0, }
    //{userId=10918, username=titanaly11, realname=方法1, expTime=1146510, bankMoney=1317886229, bankPasswd=243b6503f2e3e83faccc89830aca1d91, ifAvailable=1, password=1bbd886460827015e5d605ed44252251, money=100000, }
        }
    //初始化時(shí)的變量參數(shù)
    //public User(long userId,String playServerId) {
    // this.userId = userId;
    // this.playServerId = playServerId;
    //}
    @OnMethod(clazz = "com.gameplus.core.model.oracle.user.User", method="<init>")
         public static void bufferMonitor(@Self Object self,long userId,String gameId){ 
            printFields(self);
            println(userId);
            println(gameId);
    //結(jié)果
    //{userId=0, username=null, realname=null, expTime=0, bankMoney=0, bankPasswd=null, ifAvailable=0, password=null, money=0, nickName=null, iconNum=0, tim=null, infullCount=0, userToken=null, onlineTime=0,}
    //10918
    //222
        }
    //打印系統(tǒng)參數(shù)
    static {
    println("System Properties:");
    printProperties();
    println("VM Flags:");
    printVmArguments();
    println("OS Enviroment:");
    printEnv();
    exit(0);
    }
    //打印程序執(zhí)行關(guān)系
    //LobbyAction中所有方法執(zhí)行的執(zhí)行順序
    @OnMethod(clazz="com.gameplus.action.siteLobby.LobbyAction", method="/.*/",
                  location=@Location(value=Kind.CALL, clazz="/.*/", method="/.*/"))
        public static void n(@Self Object self, @ProbeClassName String pcm, @ProbeMethodName String pmn,
                             @TargetInstance Object instance, @TargetMethodOrField String method){ // all calls to the methods with signature "(String)"
            println(Strings.strcat("Context: ", Strings.strcat(pcm, Strings.strcat("#", pmn))));
            println(instance); //被調(diào)用目標(biāo)對(duì)象
            println(Strings.strcat("",method)); //被調(diào)用方法
    //Context: com/gameplus/action/siteLobby/LobbyAction#bankPage
    //$Proxy18@807c31
    //getBankPage
    //Context: com/gameplus/action/siteLobby/LobbyAction#bankPage
    //{userEx=10, leverRestriction=6, isDefaultPasswd=0, bankPassEqGamePass=0, moneyTransferMsg=S, isSimplePasswd=0, mobile=null}
    //get
    //Context: com/gameplus/action/siteLobby/LobbyAction#bankPage
    //6
    //intValue
    //Context: com/gameplus/action/siteLobby/LobbyAction#bankPage
    //{userEx=10, leverRestriction=6, isDefaultPasswd=0, bankPassEqGamePass=0, moneyTransferMsg=S, isSimplePasswd=0, mobile=null}
    //get
    }
    posted @ 2012-02-02 17:38 不高興 閱讀(2303) | 評(píng)論 (3)編輯 收藏
     
        最近在進(jìn)行MyEclipse啟動(dòng)速度優(yōu)化,優(yōu)化發(fā)現(xiàn)MyEclipse報(bào)這個(gè)錯(cuò)誤,關(guān)掉它的提示后不影響正常的使用.

        用visualvm監(jiān)控發(fā)現(xiàn)heap memory正常運(yùn)轉(zhuǎn),查看了下配置參數(shù),發(fā)現(xiàn)這個(gè)參數(shù)-XX:+DisableExplicitGC(禁止system.gc()的調(diào)用,gc過(guò)程完全有jvm控制),懷疑他就是報(bào)錯(cuò)的原因,去掉后一切正常.估計(jì)是MyEclipse在發(fā)現(xiàn)heap memory不足是顯示調(diào)用了gc方法,然后gc方法不被jvm接受,當(dāng)內(nèi)存達(dá)到MyEclipse的報(bào)警值時(shí)報(bào)錯(cuò)提醒 


    posted @ 2012-02-01 11:03 不高興 閱讀(5332) | 評(píng)論 (3)編輯 收藏
     

    http://suhuanzheng7784877.iteye.com/blog/1170585

    “武林至尊,寶刀屠龍。號(hào)令天下,莫敢不從。倚天不出,誰(shuí)與爭(zhēng)鋒。”。這個(gè)是我們的射雕英雄郭靖留給倚天屠龍年代的唯一財(cái)富,小說(shuō)中,這筆財(cái)富在反元起義軍中起到了很重要的作用。咱們不說(shuō)新版的小說(shuō)改動(dòng)吧,就用經(jīng)典版來(lái)說(shuō)事。倚天劍里面是《九陰真經(jīng)》,而屠龍刀里面是《武穆遺書(shū)》(最新版小說(shuō)已經(jīng)修改),單獨(dú)來(lái)講,倚天劍和屠龍刀都是利器,作為武器,十分鋒利。紫衫龍王都說(shuō)過(guò),滅絕師太憑著倚天劍的鋒利,戰(zhàn)勝了她,所以她想用屠龍刀雪恨,這當(dāng)然是紫衫龍王的一種自嘲了。之后還有張無(wú)忌決戰(zhàn)光明頂時(shí)使用白眉鷹王的白虹劍與倚天劍對(duì)抗,白虹劍也是一柄罕見(jiàn)的利器了,不過(guò)還是玩完了,由此足見(jiàn)倚天劍的威力還是十分給力的!在倚天劍面前,其他的武器立刻成為了神馬。但是很多人都不知道了藏在倚天劍的真正價(jià)值。

    我們往往都是從即時(shí)反映出來(lái)的效應(yīng)來(lái)發(fā)現(xiàn)一個(gè)東西的價(jià)值,就好比剛剛邁入軟件領(lǐng)域的很多俠客們。記得當(dāng)時(shí)筆者在校時(shí)身邊就一直有很多消息宣傳Java如何如何好,J2EE(當(dāng)時(shí)叫J2EE)如何如何,然后很多人就去學(xué)習(xí)Java,報(bào)各種的培訓(xùn)班,買(mǎi)來(lái)很多入門(mén)書(shū)籍。后來(lái)又有一股風(fēng)飄來(lái)說(shuō)微軟的.NET如何如何強(qiáng)悍,比Java優(yōu)越在哪里哪里。后來(lái)又興起PHP技術(shù),這就是有名的3P爭(zhēng)霸戰(zhàn),(當(dāng)然ASP.NETJavaEE不光是aspjsp)。各個(gè)論壇的帖子爭(zhēng)論也一直喋喋不休,甚至出現(xiàn)技術(shù)、公司、人身攻擊。不僅僅是不同語(yǔ)言之間的斗爭(zhēng),就連相同語(yǔ)言?xún)?nèi)部也有這樣的斗爭(zhēng),比如,Struts2、JSF、Spring Web MVC之間的競(jìng)爭(zhēng),為此筆者來(lái)寫(xiě)了一篇《Struts2JSF的瑜亮之爭(zhēng)》,當(dāng)時(shí)沒(méi)有涉及到Spring Web MVC,實(shí)事求是,因?yàn)楣P者當(dāng)時(shí)確實(shí)沒(méi)接觸過(guò)Spring Web MVC。再比如HibernateMyBatis(原先的IBatis)的爭(zhēng)論,ExtJSJquery的爭(zhēng)霸,咱們是用Tomcat還是Jetty,數(shù)據(jù)庫(kù)到底用哪個(gè)產(chǎn)品啊等等。就連相同語(yǔ)言?xún)?nèi)部,相似功能軟件之間都有這么多的爭(zhēng)論。這對(duì)于軟件使用者來(lái)說(shuō)其實(shí)是好事,有爭(zhēng)論,有非議,有批評(píng),才更有生命力。

    其實(shí)還是那句話,真正的高手其實(shí)不在乎是用什么技術(shù),甚至不在乎使用什么語(yǔ)言實(shí)現(xiàn)軟件。高手真正在乎的是如何將一個(gè)技術(shù)或者說(shuō)語(yǔ)言發(fā)揮到極致,甚至著眼于大局,將各種技術(shù)提取優(yōu)點(diǎn),用它的優(yōu)點(diǎn),整合其他技術(shù)規(guī)避他的缺點(diǎn)。就比如說(shuō)很多做電信行業(yè)系統(tǒng)的,做業(yè)務(wù)處理的時(shí)候僅僅將Java作為整個(gè)大系統(tǒng)的邏輯控制層,Java僅僅接收請(qǐng)求,負(fù)責(zé)一個(gè)業(yè)務(wù)分發(fā)的角色,而底層的核心業(yè)務(wù)的處理則采用中間件整合C++代碼來(lái)完成整個(gè)業(yè)務(wù)的處理。

    就像之后張無(wú)忌與趙敏手下的劍客——阿大進(jìn)行劍術(shù)比試,阿大手中使用的就是之前咱們提到的——很給力的倚天劍啊,而張無(wú)忌手中拿的僅僅是一把木劍,張無(wú)忌規(guī)避倚天劍的鋒芒,使用陰柔的太極劍法將阿大打敗。張無(wú)忌那時(shí)候已經(jīng)算是個(gè)頂級(jí)高手了,他知道如何讓倚天劍發(fā)揮不了它的長(zhǎng)處,他也知道如何利用太極劍法發(fā)揮自己手中這把小小木劍的長(zhǎng)處。張無(wú)忌就是無(wú)論使用何種兵器對(duì)他來(lái)說(shuō)都差不多,只能說(shuō)如果是絕世兵器在他手中發(fā)揮得更加淋漓盡致罷了。關(guān)鍵還是他的修為在那里,基本上還是以武學(xué)修為做為勝負(fù)的關(guān)鍵因素。

    從中也可以看出所謂編程語(yǔ)言、技術(shù)、中間件產(chǎn)品不過(guò)是實(shí)現(xiàn)某種商業(yè)目的的一種手段罷了,所有的技術(shù)幕后都是一樁樁充滿(mǎn)銅臭味的商業(yè)運(yùn)作罷了。當(dāng)時(shí)年輕的筆者懷著崇敬的心等待著Java陣營(yíng)將.NET陣營(yíng)徹底打敗,以證明當(dāng)初自己的選擇是正確的,誰(shuí)能想到最先趴下繳械的居然就是咱們崇敬的Sun啊。被甲骨文收購(gòu)后,逼迫JavaEye改名為ITEye,現(xiàn)在又向Google索取巨額的Java侵權(quán)費(fèi)用,不得不讓我們感嘆,何時(shí)Java也充滿(mǎn)了這種商業(yè)的銅臭味,沒(méi)辦法咱們只能接受,一切一切的技術(shù)推進(jìn)都是商業(yè)巨頭們的運(yùn)作結(jié)果。所以技術(shù)僅僅是個(gè)手段工具罷了,如果將它作為一種崇拜對(duì)象,成為自己生命不可或缺的部分。哥們兒,姐們兒,隨著時(shí)間的推移,可能會(huì)讓你越來(lái)越心寒哦。

    后來(lái)倚天劍和屠龍刀終于再次匯合,才將刀劍真正的價(jià)值體現(xiàn)出來(lái),原來(lái)將他們的優(yōu)點(diǎn)結(jié)合在一起,互相利用各自的鋒芒,將潛藏里面的東東挖掘出來(lái)。我們?cè)贋榭蛻?hù)做軟件技術(shù)解決方案的時(shí)候也可以將不同技術(shù)、不同語(yǔ)言的優(yōu)勢(shì)融合進(jìn)來(lái),形成一個(gè)改造后的大融合系統(tǒng),取長(zhǎng)補(bǔ)短,將軟件發(fā)揮到最極致的功能,有點(diǎn)瑕疵不怕,怕的是不能拋棄門(mén)戶(hù)之見(jiàn)的執(zhí)著,依然死守原有規(guī)矩,不肯也不敢進(jìn)行技術(shù)思想上的大解放。

    當(dāng)然,可以理解一點(diǎn)的就是,可能對(duì)于比較感情化的朋友來(lái)說(shuō),對(duì)于第一門(mén)認(rèn)真學(xué)習(xí),并花了大把時(shí)間的技術(shù)抱有很深的感情,對(duì)于第一門(mén)認(rèn)真下功夫?qū)W習(xí)的技術(shù)語(yǔ)言,程序員一般都有一種微妙的感情在里面。這種感情筆者稱(chēng)之為“技術(shù)的初戀”,“初戀”嘛,就意味著當(dāng)事人想讓這段感情更加持久,不希望,當(dāng)然更不允許任何人對(duì)當(dāng)事人的“初戀”對(duì)象說(shuō)一點(diǎn)點(diǎn)不好,所以大家一般從各大論壇上看到的各種技術(shù)、語(yǔ)言之間的口水戰(zhàn)都發(fā)生在剛剛進(jìn)入某技術(shù)領(lǐng)域的朋友。這些朋友也不允許其他技術(shù)陣營(yíng)的人來(lái)對(duì)自己的“技術(shù)初戀”進(jìn)行所謂的評(píng)頭論足,指指點(diǎn)點(diǎn)。這種感情是純潔的,是高尚的。將心比心,大家都是從菜鳥(niǎo)一步步成長(zhǎng)起來(lái)的,都是從不懂的時(shí)候慢慢找資源學(xué)習(xí)熬過(guò)來(lái)的。在學(xué)習(xí)的過(guò)程中大家或多或少有一些收獲和小成就,這就好比這個(gè)“初戀情人”給你這個(gè)當(dāng)事人一點(diǎn)點(diǎn)愛(ài)情的獎(jiǎng)勵(lì)似的。也有朋友將這種愛(ài)情“獎(jiǎng)勵(lì)”的過(guò)程和經(jīng)驗(yàn)分享出來(lái),就是我們看到很多技術(shù)Blog文章,無(wú)論怎樣,都請(qǐng)看文章的朋友們尊重那些你們眼中的“菜鳥(niǎo)”,不要認(rèn)為自己多么多么NB就隨意踐踏那些淳樸程序員們的感情,踐踏這些人的勞動(dòng)成果是十分殘忍的事情,就和踐踏別人的初戀一樣??吹貌┛臀恼聝?nèi)容簡(jiǎn)單,你可以什么都不說(shuō),也可以對(duì)那些作者提一些自己的建議,給新人一些建設(shè)性的意見(jiàn),分享一些自己的經(jīng)歷。開(kāi)口就罵:什么“作者低能”、“這種問(wèn)題都問(wèn)”的人,這樣恐怕不太好吧,因?yàn)檎?qǐng)各位老鳥(niǎo)們記住,你也經(jīng)歷過(guò)那個(gè)階段的人,只不過(guò)你比人家多長(zhǎng)了幾歲,比人家接觸知識(shí)點(diǎn)的早一些罷了,沒(méi)什么值得自豪和炫耀的。除非你投胎的時(shí)候沒(méi)洗去前世的記憶,恰巧你前世又是個(gè)拿過(guò)諾貝爾獎(jiǎng)的高手甚至是愛(ài)因斯坦轉(zhuǎn)世,那另當(dāng)別論,你一出生你就成功了……對(duì)新人,多一些鼓勵(lì),善莫大焉。

    我只是要說(shuō),請(qǐng)各位純情的程序員朋友們記住一首歌——《當(dāng)愛(ài)已成往事》:只要有愛(ài)就有痛啊,有一天你會(huì)知道,人生沒(méi)有它(當(dāng)然指具體的技術(shù)實(shí)現(xiàn)或者編程語(yǔ)言了)并不會(huì)有什么不同。選擇還是很多的,只要能順應(yīng)商業(yè)潮流,為客戶(hù)解決問(wèn)題,發(fā)揮自己所學(xué)東西的最大優(yōu)勢(shì)就好嘍。無(wú)論您是刀狂還是劍癡,重要的是刀劍合一,無(wú)刀無(wú)劍,一枚繡花針也能揮灑自如,呵呵~

    posted @ 2011-12-29 16:00 不高興 閱讀(428) | 評(píng)論 (2)編輯 收藏
     
    http://suhuanzheng7784877.iteye.com/blog/1115472

    很多人認(rèn)為我們程序員不會(huì)做人,至少認(rèn)為我們?cè)谌穗H交際方面缺乏技巧。程序員一遇到人際方面的事情就發(fā)憷,和售前人員,領(lǐng)導(dǎo),甚至是客戶(hù)都不太會(huì)溝通,遇到非技術(shù)的事情,就慌了,往往作出的決定比較茫斷!

     

    反正筆者確確實(shí)實(shí)覺(jué)得是有這樣的問(wèn)題,不說(shuō)大的方面吧,我們就看看在工作中如何做順?biāo)饲椤?赡軜?biāo)題有點(diǎn)大,一般職場(chǎng)說(shuō)不上什么恩德吧,但是總可以說(shuō)得上是順?biāo)饲榘?。各位看過(guò)金庸小說(shuō)的朋友覺(jué)得誰(shuí)值得我們學(xué)習(xí)呢?筆者覺(jué)得做人際交流的典范就是韋小寶,他有很多值得我們學(xué)習(xí)的地方,以后比這還要說(shuō)他,咱們這次只單看看他如何做順?biāo)饲?,施恩給別人的。從中我們以后遇到非技術(shù)事情要處理的時(shí)候是不是腦中多了一個(gè)Java監(jiān)視器類(lèi),觸發(fā)事件后時(shí)刻提醒我們:“如果是韋小寶,他會(huì)怎么做?他該怎么做?他會(huì)如何說(shuō)話?”。

     

    當(dāng)康熙讓韋小寶剿滅王屋山的時(shí)候,韋小寶特地找了個(gè)叫趙良棟的,第一,韋小寶不會(huì)打仗,即便是小規(guī)模的剿匪,他自己都說(shuō):“老子不是干這種事的人才”,但是康熙下令讓他就得這么做。第二,他不想讓第N個(gè)小老婆——曾柔受傷害,要不就虧大了。第三,王屋山和天地會(huì)(當(dāng)然放到現(xiàn)在咱們叫反和諧社會(huì)的不法組織)有關(guān)聯(lián),如果讓陳近南知道后果肯定會(huì)讓韋小寶不爽。第四,王屋山本身和韋小寶本人的交情也不錯(cuò),也算是以前建立起的人脈財(cái)富吧,如果干掉,那么意味著以前的人脈關(guān)系維系全他娘玩完了。韋小寶若是嚴(yán)格執(zhí)行君令,那么,有三個(gè)損失,只有一個(gè)好處,不違背康熙的命令。好的,我們的韋爵爺才不會(huì)像Java虛擬機(jī)那么忠實(shí),編譯后class文件是什么樣的,我們的編譯器就怎么執(zhí)行。韋小寶則更像是康熙的實(shí)現(xiàn)類(lèi),康熙是接口,下個(gè)命令(接口方法),很抽象,他只關(guān)心結(jié)果,不關(guān)心過(guò)程,那么韋小寶如何實(shí)現(xiàn)這個(gè)接口,完全由韋小寶負(fù)責(zé)具體的細(xì)節(jié),之后康熙的目的達(dá)到了,王屋山這個(gè)匪政權(quán)確實(shí)沒(méi)了,那康熙的目的就達(dá)到了(盡管康熙在實(shí)現(xiàn)類(lèi)——韋小寶身邊安裝了監(jiān)聽(tīng)器),韋小寶的人情也做足了,第一,執(zhí)行命令的同時(shí),收編曾柔小老婆,曾柔感覺(jué)韋小寶好很偉,很強(qiáng)大,既挽救了師兄弟,又救了自己,真帥啊,美人到手。第二,對(duì)于天地會(huì)那邊也有交代,陳近南知道后只會(huì)向其他兄弟夸耀我這個(gè)徒弟有雄略,有義氣,有俠氣。只會(huì)更喜歡這個(gè)徒弟,后來(lái)陳近南也說(shuō)了一句,“以后天地會(huì)就要靠你了”,完全有繼承他衣缽的意思,師父面子上也有光,在反政府武裝組織天地會(huì)中也更有聲望,聲譽(yù),名望到手。第三,王屋山其他兄弟也是對(duì)他感激涕零,敢于冒死命,因?yàn)槟菚r(shí)候反政府組織內(nèi)部最講究的就是個(gè)義氣,大多數(shù)江湖反政府組織大多窮苦出身,都是哥們兒義氣當(dāng)先,咱們看《書(shū)劍恩仇錄》中的紅花會(huì)也能看得出來(lái),那么這樣韋小寶人心到手。

     

    韋小寶這么做真叫一個(gè)高明,也是他小小年紀(jì)做事如此周詳,實(shí)屬難得,也許是官場(chǎng)的歷練讓他做什么事情都先權(quán)衡以下各方利弊,當(dāng)然對(duì)于自己的利益權(quán)重是最大的。之后韋小寶還有很多案例都說(shuō)明韋小寶做人情做到十足~不但自己的目的達(dá)到了,還為其他人某了本身不應(yīng)該有的福利待遇。帶領(lǐng)施瑯炮轟神龍島,不僅將自己最忌憚的神龍教弄得七零八落,還讓一個(gè)軍事人才施瑯有了一展才華的機(jī)會(huì),后來(lái)施瑯也是為了還他人情,讓韋小寶在通吃島住了一段悠閑的日子。索額圖、明珠、康親王這些在政治官場(chǎng)上老油條也感懷韋小寶在官場(chǎng)上的照顧人情,當(dāng)然這些人出于政治目的,但是韋小寶對(duì)官場(chǎng)政治著實(shí)不怎么看重,索性做順?biāo)饲?,他的潛臺(tái)詞就是:“你們這些人不必揣摩圣意了,不必為難。我和皇帝做個(gè)雙簧戲,你們立刻知道皇上的意思了~”,主戰(zhàn)派和主和派心里就有底了。

     

    其實(shí)說(shuō)了那么多,都是想提醒自己,假如這種非技術(shù)性質(zhì)的事情到自己頭上后該怎么辦?我覺(jué)得第一還是先想到與此事相關(guān)的人,最好有個(gè)關(guān)系——角色映射圖,一個(gè)事情的處理關(guān)系到哪些人,需要明確下來(lái)。第二就是一個(gè)事情該怎么做,有幾條途徑。如果只有想來(lái)想去一條路,不妨先將此事放放,轉(zhuǎn)一下注意力,再回頭來(lái)看看是否還有別的路可走(一般情況下應(yīng)該會(huì)有另外的途徑吧)。第三,就是根據(jù)每一條途徑去看每一個(gè)關(guān)系人的受益和損失程度。根據(jù)每個(gè)人在此事件中擔(dān)當(dāng)?shù)慕巧詈笳页鲆粋€(gè)最好的解決事情的途徑出來(lái)。雖然很多讀者都會(huì)說(shuō),照你這么做事,太麻煩了吧,不必吧,用得著嗎?值得嗎?筆者則認(rèn)為哪怕是多么小的事情,這么做也不為過(guò),雖然麻煩點(diǎn),但是細(xì)節(jié)決定成敗啊,厚人薄己得人心啊。

     

    還有就是很多人有疑問(wèn),就是按照你這么做事情,過(guò)于麻煩,如果遇到即使就要做出決策的突發(fā)事情,比如就是上司、客戶(hù)突然來(lái)了電話,那該怎么辦?第一,遇到這種及時(shí)解決的問(wèn)題,那么最起碼“現(xiàn)在沒(méi)有想好,需要和XXX商量商量才能決定”;“哦,不好意思,我現(xiàn)在電話說(shuō)不清楚,環(huán)境比較雜亂”,稍后給您打過(guò)去;“容我想一想”這種緩兵之計(jì)的話總能說(shuō)出口吧??偙饶欠N腦子一熱就做出決策的沖動(dòng)選擇要好得多吧。當(dāng)然了,如果突發(fā)事件對(duì)事件關(guān)系的每個(gè)當(dāng)事人都有好處,對(duì)于你來(lái)說(shuō)又是十分有把握輕而易舉就能完成,你當(dāng)然就能及時(shí)應(yīng)答下來(lái)了。這不是沖動(dòng),是自信!如果說(shuō)遇到的客戶(hù),就必須要你當(dāng)時(shí)做決策,筆者感覺(jué)利用以上方法經(jīng)驗(yàn)比較多了,腦中的反應(yīng)也會(huì)逐漸變快,作出的決策可能也比沒(méi)有聯(lián)想的決策付出的代價(jià)要小一些。說(shuō)起來(lái)這個(gè)有點(diǎn)像咱們之前系統(tǒng)訪問(wèn)負(fù)載均衡中的最優(yōu)化策略了。這樣,事件的關(guān)系人也會(huì)對(duì)你懷有一種感懷的心,人情也做足了。你自己從中得到了下屬的信任,上司的認(rèn)可,明智客戶(hù)的獎(jiǎng)勵(lì)(哪怕是口頭上的夸贊呢,也行了),你自己從中也是得到了鍛煉,經(jīng)驗(yàn),做事也越來(lái)越老練。這樣各位朋友人生的路也會(huì)變得越來(lái)越寬,其他人也愿意和您這樣能為他們利益著想的人公事。

    posted @ 2011-12-29 15:50 不高興 閱讀(462) | 評(píng)論 (2)編輯 收藏
     

    執(zhí)著能殺人,在執(zhí)著殺死你之前,請(qǐng)將它殺死。看過(guò)《天龍八部》的人一定記得那經(jīng)典的一段珍瓏棋局吧。我們就從這盤(pán)棋局說(shuō)起。逍遙掌門(mén)讓蘇星河布置珍瓏棋局是為了替逍遙派清理門(mén)戶(hù),繼承自己的衣缽。參與這場(chǎng)棋局的主要有四人(范百齡就算了吧),段譽(yù)、慕容復(fù)、段延慶、虛竹。其中,虛竹不怎么會(huì)下棋,假借段延慶的傳音入耳和他的棋藝通過(guò)了面試,最終獲得逍遙掌門(mén)的絕世內(nèi)功。我們就看慕容復(fù)的執(zhí)著是如何差點(diǎn)要他命的。慕容復(fù)胸懷大志,參與這個(gè)棋局其實(shí)目的也比較簡(jiǎn)單,擴(kuò)大自己聲望,結(jié)交江湖名士,為自己日后富國(guó)鋪路。但是慕容復(fù)對(duì)于勝敗結(jié)果太過(guò)于執(zhí)著了,對(duì)勝負(fù)總是看得比誰(shuí)都重要,就好像慕容復(fù)的心中就不允許失敗一樣,對(duì)武功的高低執(zhí)著、對(duì)棋局的勝敗執(zhí)著,對(duì)復(fù)國(guó)的事業(yè)依然執(zhí)著??纯此c段譽(yù)的區(qū)別,這個(gè)珍瓏棋局變幻百端,因人而施,愛(ài)財(cái)者因貪失誤,易怒者由憤壞事。段譽(yù)之前之所以敗,是因?yàn)閻?ài)心太重,不肯棄子;慕容復(fù)之失,由于執(zhí)著權(quán)勢(shì),雖然勇于棄子,卻說(shuō)什么也不肯失勢(shì)。

    棋局的勝敗都看得如此重要,那對(duì)于其他方面的勝負(fù)就不必說(shuō)了,在少林寺因?yàn)閿∮诙巫u(yù)的六脈神劍而羞愧得想自殺,這難道是一個(gè)想問(wèn)鼎中原的未來(lái)復(fù)國(guó)君王應(yīng)該做的事情?

    過(guò)分的執(zhí)著往往意味著急功近利,蕭遠(yuǎn)山執(zhí)著于他的報(bào)仇,卻從沒(méi)想到他的親生兒子會(huì)因?yàn)樗臑E殺而被整個(gè)武林誤會(huì),成為武林的公敵,才會(huì)有后來(lái)的少林寺混戰(zhàn)。慕容復(fù)的父親——慕容博也因?yàn)閳?zhí)著于他的復(fù)國(guó)之路,造成了武林的恩恩怨怨。鳩摩智執(zhí)著于他的武功,功名,到最后留給后人的卻不是他的獨(dú)門(mén)武功,而是他的傳教經(jīng)典和佛法,直至今日給我們現(xiàn)代人,留下的究竟是豐碑還是悲風(fēng)呢。段譽(yù)執(zhí)著于王語(yǔ)嫣的癡情,其間路途也是坎坷萬(wàn)分,幸運(yùn)的是金庸給了他一個(gè)美滿(mǎn)的結(jié)局,換到真實(shí)世界恐怕就不那么幸運(yùn)了。蕭遠(yuǎn)山與慕容博到后來(lái)他們?cè)谏倭钟龅搅藷o(wú)名曾,在無(wú)名曾的安排下先讓蕭遠(yuǎn)山看著慕容博死去,之后在自己動(dòng)手讓蕭遠(yuǎn)山假死。等兩個(gè)人醒來(lái)后,問(wèn)他們:“你二人由生到死、由死到生的走了一遍,心中可還有甚么放不下?倘若適才就此死了,還有甚么興復(fù)大燕、報(bào)復(fù)妻仇的念頭?”。二人大徹大悟,終于不再執(zhí)著于過(guò)去的恩恩怨怨,功名富貴,專(zhuān)心研究佛法,參禪。實(shí)際上無(wú)名曾殺死的不僅僅是過(guò)去的兩個(gè)“大惡人”,更深的一層實(shí)際是借無(wú)名曾之手殺死了他們自己的執(zhí)著。

    不說(shuō)《天龍八部》,比如《連城訣》中爾虞我詐的三個(gè)師兄弟;《射雕英雄傳》的歐陽(yáng)鋒;《鹿鼎記》的陳近南;《笑傲江湖》的岳不群、左冷禪、任我行、林平之;《倚天屠龍記》的謝遜、成昆;《碧血?jiǎng)Α分械慕鹕呃删?#8230;…多少人因?yàn)檫^(guò)分的執(zhí)著而掩蓋了本應(yīng)該光彩的人生?

    那我們呢?我們?cè)诟?jìng)爭(zhēng)激烈、追名逐利的今天是否也因?yàn)檫^(guò)分的執(zhí)著而彎曲了我們?cè)械娜松壽E?雖然說(shuō)在現(xiàn)代我們不會(huì)因?yàn)檫^(guò)于執(zhí)著而獻(xiàn)出寶貴的生命,但是回想一下,我們是不是也因?yàn)槲覀冞^(guò)分的執(zhí)著于某些事情而錯(cuò)過(guò)了很多不應(yīng)該錯(cuò)過(guò)的東西呢?或者還可以說(shuō),我們是不是因?yàn)閳?zhí)著導(dǎo)致我們?cè)械乃?、能力、智慧、知識(shí)都打了折扣。張無(wú)忌在學(xué)《乾坤大挪移》的時(shí)候到最后有幾句話不是很明白,如果他執(zhí)著于弄明白,估計(jì)結(jié)果和陽(yáng)頂天一樣。當(dāng)令狐沖和任盈盈同處大車(chē)之中,徜徉于青紗帳外的大路上時(shí),對(duì)岳靈珊癡情的執(zhí)著終于消失 了,他也得到心靈上的解脫。

    一個(gè)項(xiàng)目苦戰(zhàn)數(shù)月未有結(jié)果、一段程序苦苦調(diào)試很長(zhǎng)時(shí)間問(wèn)題依舊,這個(gè)時(shí)候我們是否可以先放一放,看看外面的風(fēng)景,泡一杯茶,慢慢品味苦中有甜的滋味,過(guò)后調(diào)試程序換一種思路,項(xiàng)目管理大膽嘗試一下新的管理方式,沒(méi)準(zhǔn)可以發(fā)現(xiàn)新的天地。如果一味的朝一個(gè)方向死走,一條道走到底,幸運(yùn)的人可以走出去,祝賀你當(dāng)時(shí)相對(duì)地選對(duì)了路,不幸運(yùn)的人就會(huì)鉆牛角尖了。搞研發(fā)的適時(shí)可能會(huì)去做manager的職位,也可以去搞product售前等等,就是因?yàn)楹芏嚅_(kāi)發(fā)人員自身很清高,不愿意,甚至說(shuō)是不屑做人際、關(guān)系上的事情。那么你對(duì)人家不屑,人家對(duì)你也不屑。有時(shí)候咱們是要放下那些執(zhí)念,勇于改變以前的想法和初衷。因?yàn)橛锌赡墚?dāng)初的理想、志向、目標(biāo)是不成熟的,而我們還依然一直執(zhí)著下去……

    當(dāng)然筆者年齡、閱歷、經(jīng)驗(yàn)等等均有限,僅僅是將我個(gè)人的經(jīng)歷和心得分享出來(lái),絕對(duì)不是站在一個(gè)教育者的高姿態(tài)來(lái)教育某某某,絕無(wú)此意。僅僅是下次看到自己曾經(jīng)走過(guò)的彎路時(shí)多一份沉重,時(shí)刻提醒自己罷了,“執(zhí)著會(huì)殺人,當(dāng)它沒(méi)有殺死你的時(shí)候趁早消滅它”。放棄執(zhí)著,也許你會(huì)看到另一片你從未看到過(guò)的天地在迎接你。

    posted @ 2011-12-29 15:48 不高興 閱讀(264) | 評(píng)論 (1)編輯 收藏
     
    http://suhuanzheng7784877.iteye.com/blog/1096125

    用電影《東邪西毒》的臺(tái)詞作為開(kāi)頭,“任何人都可以狠毒,只要你嘗試過(guò)什么是嫉妒,我不在乎別人怎么看我,我只是不想別人比我更開(kāi)心。”。嫉妒心是人人都會(huì)有的,除非真正看破紅塵的人,不在此列,哀莫大于心死,心死的人再無(wú)好勝之心,可以說(shuō)嫉妒心也遠(yuǎn)離他而去,不過(guò)我們這些打工的就算了,肯定是個(gè)凡人吧。

    《笑傲江湖》里面的林平之,剛開(kāi)始是一個(gè)誠(chéng)實(shí)、有正義感的年輕人。金庸剛開(kāi)始把他寫(xiě)得也是有光彩的,就在福州小店,為假扮店老板的勞得諾和岳靈珊打抱不平,就能看出從骨子里他還是有正義感的。慘遭青城派滅門(mén),林平之沿路到各個(gè)鏢局分局去投奔。那個(gè)時(shí)候他也完全摒棄了富二代的架子,忍辱負(fù)重,慘遭眾多磨難,終于拜了岳不群為師??梢哉f(shuō)在《笑傲江湖》前面的章節(jié),作者對(duì)林平之絕對(duì)是贊許的。試想一個(gè)富二代遭遇了如此重大的家庭變故,還能放下架子,忍辱負(fù)重。寧愿去給人家當(dāng)小徒弟,也不去投靠外公。足可以看出,林平之前期有志氣、有骨氣、有俠氣。而且林平之說(shuō)過(guò)他只想靠一己之力報(bào)仇雪恨,絕不愿意假手其他人。如此光明磊落,說(shuō)實(shí)話,沒(méi)有看過(guò)笑傲江湖的讀者,前期大家一定以為這應(yīng)該就是《笑傲》的主角了吧。事宜愿為,林平之最后的下場(chǎng)大家是知道的,很多讀者對(duì)他都是惋惜的態(tài)度。筆者對(duì)林平之的態(tài)度是:對(duì)他的遭遇感到同情,對(duì)他的毅力表示敬佩,對(duì)他的殘忍表示憤怒。對(duì)于他的結(jié)果,我認(rèn)為原因很多,但究其個(gè)人原因,我認(rèn)為主要是因?yàn)榱制街募刀市摹S腥藛?wèn),他嫉妒誰(shuí)?毀了他的是余滄海、是木駝峰、是岳不群。林平之應(yīng)該恨得是他們,何來(lái)嫉妒之心。是的!毀了福威鏢局的是他們。林平之也是恨他們,但是他嫉妒的不是這些中年人,他嫉妒的是比他大不了幾歲的令狐沖。筆者之前也確實(shí)有疑問(wèn),為何林平之對(duì)令狐沖如此恨得咬牙切齒。后來(lái)明白是嫉妒。

    令狐沖出身不如林平之,就一個(gè)孤兒,沒(méi)有什么家庭背景,和林平之家世相比甚遠(yuǎn);令狐沖長(zhǎng)相不如林平之俊俏,書(shū)中有描寫(xiě),令狐沖是一個(gè)高大的男子,林平之是福建那邊的人,長(zhǎng)相比較像母親,俊俏得很,真得是現(xiàn)在很多女孩子心目中的奶油小生;林平之教育良好,見(jiàn)過(guò)大世面,令狐沖就是一個(gè)窮小子,更談不上什么高等文化了,識(shí)字,讀書(shū)已經(jīng)不錯(cuò)了,林平之的涵養(yǎng)在令狐沖之上。那么他到底有什么理由嫉妒他呢。正是因?yàn)橐陨显?,林平之就覺(jué)得自己有優(yōu)越感,進(jìn)入華山派后處處與令狐沖進(jìn)行比較,從令狐沖初學(xué)獨(dú)孤九劍打敗封不平、成不憂后。林平之就開(kāi)始不爽這個(gè)大師兄了,他嫉恨得不是別的,就是他的際遇不如令狐沖。之后祖千秋設(shè)酒,問(wèn)令狐沖和其他華山弟子敢不敢喝就,只有林平之一個(gè)人敢站出來(lái)說(shuō):“有什么不敢的!”實(shí)際上暗中已經(jīng)和大師兄較上了勁。更兼令狐沖劍法詭異,林平之不得不懷疑他的劍法從哪兒來(lái)的?雖然事后證明不是《辟邪劍譜》,但是他總是先入為主,認(rèn)為令狐沖欠了他什么似的。

    嫉妒心蒙蔽了他的雙眼和原有的良知!使他瘋狂的將一切罪過(guò)轉(zhuǎn)移給了令狐沖。林平之殺了余滄海后其實(shí)仇人只剩下岳不群,但是林平之一直認(rèn)為令狐沖才是他最大的對(duì)手,欲除之而后快。最后把真正的BOSS級(jí)的敵人忽略了。如果他摒棄一顆嫉妒心,目標(biāo)會(huì)更明確,如果他學(xué)完辟邪劍法后繼續(xù)韜光養(yǎng)晦,先故意輸給余滄海(反正學(xué)完了辟邪劍法自保肯定沒(méi)問(wèn)題),之后回到岳不群身邊伺機(jī)先干掉這個(gè)BOSS級(jí)人物,之后再除掉余滄海,估計(jì)林平之獲得的東西會(huì)更多,最好的結(jié)果是岳不群辛苦半生的成果給了林平之做了嫁衣(哦,也許說(shuō)嫁妝更合適)。

    說(shuō)了這么多咱們會(huì)過(guò)來(lái)看看自己的人生是不是也有過(guò)類(lèi)似的現(xiàn)象,幾個(gè)比較好的同事。剛開(kāi)始大家都是懷著一顆純真的心交往的。大家都是肝膽相照,有說(shuō)有笑,像兄弟一樣。尤其是那些背井離鄉(xiāng)的同志們更有一種身在異鄉(xiāng),互相依賴(lài),互相扶持的感情在里面。久而久之,因?yàn)楣镜母鞣N原因吧,將你不錯(cuò)的同事、同學(xué)、朋友的職位調(diào)了一級(jí),工資翻了一倍?;蛘吡硪粋€(gè)同事跳槽了,各種待遇在你之上。那么你會(huì)不會(huì)心里有一絲不爽呢~呵呵,大家都不必否認(rèn),多多少少都有一點(diǎn),這是人性,回避不了。在咱們這樣一個(gè)和諧社會(huì)下的人們,正常思考的人們多多少少都有點(diǎn)嫉妒,嫉妒程度多少因人而異。我覺(jué)得有一點(diǎn)嫉妒心很正常,而且也是好事,它能刺激你進(jìn)步,在自己的心里就會(huì)給自己一種壓力:“他都到了那個(gè)程度,那我呢?大家是同一起點(diǎn)~”。積極的人,會(huì)因?yàn)檫@細(xì)微的嫉妒,自強(qiáng)、奮進(jìn)、朝著自己的新目標(biāo)前進(jìn)。但是如果嫉妒心過(guò)重,就像林平之一樣,遷怒旁人。典型的特點(diǎn)就是,坐在辦公室里整天抱怨,怨天尤人。怨公司為何不給自己這樣的待遇、怨機(jī)遇為何不降臨自己頭上、怨社會(huì)為何如此不公平、怨同事如此不和自己交心如此一來(lái)就會(huì)蒙蔽了自己的雙眼,本來(lái)自己的規(guī)劃也會(huì)因?yàn)榉N種嫉妒心引起的浮躁、不滿(mǎn)而終究告吹。結(jié)果就是到了最后一無(wú)所有,連當(dāng)初最好的朋友,這筆財(cái)富也都失去了。其實(shí)我們靜下心來(lái)想想回頭看看,是不是那個(gè)晉級(jí)的同事真的比咱強(qiáng)呢,我覺(jué)得肯定是有的。否則用人單位的領(lǐng)導(dǎo)應(yīng)該不會(huì)傻到看不出來(lái),這個(gè)地方請(qǐng)大家不要懷有偏激心理,以一種第三者旁觀的身份審視你和你的朋友,看看他比你強(qiáng)在哪里,有時(shí)候嫉妒心蒙蔽了這個(gè)客觀地角色,而很多時(shí)候從主觀情感出發(fā)。相信以第三者的身份審視你們之間的差別,一定能找到你不足的地方。OK,這就達(dá)到目的了,這樣我們找找平衡,就知道確實(shí)他比我高一籌,我應(yīng)該吸取教訓(xùn),自己還需努力啊。這樣不僅僅是事業(yè)上沒(méi)有什么損失,您的人生也進(jìn)入了一個(gè)“吸星大法”的境界。

    還有一點(diǎn)就是比較敏感的薪資問(wèn)題,經(jīng)常看到論壇上牛人曬自己的工資。更有39k女,43k男等貼盛行一時(shí)。其實(shí)套用一句網(wǎng)絡(luò)上的話“網(wǎng)管上輩子都是折翼的天使,搞軟件開(kāi)發(fā)的上輩子都是身懷絕技的乞丐”。積極地一面是覺(jué)得很有希望,上面有牛人,看來(lái)還是有希望的,到不了人家那種牛級(jí)別的,退一步也能做小牛或者小小牛級(jí)別的也行啊。消極的一面則是很多人就產(chǎn)生了嫉妒心理,之后種種的謾罵、詆毀、誣陷等等動(dòng)作一一招呼來(lái)了。嫉妒是一堵墻,他蒙蔽了你的良知、你的目標(biāo)、你的修養(yǎng)。事業(yè)高潮時(shí)常想著居安思危,低調(diào)做人處事,事業(yè)低谷時(shí),咱們就以一種平常心處事,但是學(xué)習(xí)的步伐不能停下,不能因?yàn)橐粫r(shí)的狀態(tài)影響了自己本該實(shí)現(xiàn)的目標(biāo)。也許林平之心態(tài)平和一點(diǎn),他的下場(chǎng)會(huì)更好一些。林平之的命運(yùn)掌握在金老先生的筆下,只可惜金庸老先生沒(méi)有給他這個(gè)機(jī)會(huì),但是我們呢~我們的命運(yùn)掌握在我們自己手中。我們的人生是我們自己給自己機(jī)會(huì)!摒棄我們強(qiáng)烈的嫉妒心,保留一點(diǎn)點(diǎn)可以起到積極的作用,太大了,我們就會(huì)失控。最終毀于我們自己創(chuàng)造出來(lái)的嫉妒心。

    posted @ 2011-12-29 15:47 不高興 閱讀(326) | 評(píng)論 (1)編輯 收藏
     
    搞軟件就像金庸小說(shuō)中的俠客們闖蕩江湖,快意恩仇,你死我活。有人問(wèn),職場(chǎng)真的就這么血雨腥風(fēng)嗎?職場(chǎng)就是江湖,用一句廣播語(yǔ)說(shuō):“什么是江湖?有云的地方就有天下,有人的地方就有江湖。”出世前就好比闖蕩江湖之前,對(duì)于大學(xué)生來(lái)說(shuō)就是四年的大學(xué)學(xué)習(xí)生涯,對(duì)于專(zhuān)科生來(lái)說(shuō)就是三年修煉。不過(guò)這也不是絕對(duì),有人在高中,甚至是初中就接觸了軟件開(kāi)發(fā),不過(guò)國(guó)內(nèi)比較少,我們以大多數(shù)人的情況說(shuō)事兒。

    我們就用射雕三部曲的主人公作比喻,郭靖好比受過(guò)傳統(tǒng)高等教育的人士,楊過(guò)嘛~因?yàn)榈谝凰髮W(xué)不好,我們把他當(dāng)做又回家重考的大學(xué)生吧,至于張無(wú)忌,因?yàn)闆](méi)有明確的師門(mén),我們就當(dāng)做不屑于參加高考的90后的各位“爺“們。

    郭靖為人忠厚,天資不好,學(xué)什么東西都比較慢。他的老師們都一度嫌棄他傻,學(xué)東西慢,沒(méi)希望,但是呢,郭靖最大的優(yōu)點(diǎn)就是有恒心、有毅力。笨不要緊,就怕停下腳步不前進(jìn)。之后他的啟蒙老師馬鈺就說(shuō)過(guò)其實(shí)是老師教學(xué)方式有問(wèn)題,學(xué)生學(xué)得方法也有問(wèn)題。后來(lái),馬鈺以循序漸進(jìn)的方式對(duì)郭靖進(jìn)行了疏導(dǎo),郭靖慢慢開(kāi)竅,后來(lái)有了更好的機(jī)遇,終成一代大俠。在學(xué)生時(shí)代能夠趕上一個(gè)好老師和一個(gè)愿意下苦工學(xué)的學(xué)生都挺不容易的,我們作為學(xué)生無(wú)論有再好的天資也要下苦工,否則真的是浪費(fèi)了自己的天資。如果沒(méi)有一個(gè)好的老師進(jìn)行疏導(dǎo),沒(méi)關(guān)系,互聯(lián)網(wǎng)就是我們的老師,當(dāng)然這需要我們進(jìn)行取其精華去其糟粕。只要是想學(xué)的東西,想辦法,下苦工,都是可以學(xué)到的。郭靖的例子告訴我們,是金子遇到一個(gè)機(jī)遇后總會(huì)發(fā)光的,哪怕剛起步的時(shí)候有各種各樣的問(wèn)題,只要找對(duì)了方式,再以良好的性格堅(jiān)持下去,相信結(jié)果都會(huì)不錯(cuò)。我們搞軟件開(kāi)發(fā)也是一樣的,遇到某些新技術(shù)的時(shí)候最好先自己看看怎么學(xué)習(xí),切入點(diǎn)在哪里,剛開(kāi)始研究一下學(xué)習(xí)一個(gè)新技術(shù)的方法其實(shí)是有效地,比如逼著覺(jué)得學(xué)習(xí)Swing和學(xué)習(xí)Hibernate的方法就不一樣。雖然說(shuō)都是Java領(lǐng)域的技術(shù),Swing更偏向于UI的顯示與事件監(jiān)聽(tīng)機(jī)制的運(yùn)用。那么筆者認(rèn)為Swing學(xué)習(xí)方式就是掌握組件的使用場(chǎng)景和事件觸發(fā)原理這些核心的即可,等需要構(gòu)建不同需求的界面時(shí)我們按照組建模塊的方式構(gòu)建就可以了。Hibernate則更側(cè)重于持久層對(duì)象的狀態(tài)、持久層接口調(diào)用完成CRUD、優(yōu)化使用緩存等等,所以根據(jù)不同業(yè)務(wù)設(shè)計(jì)實(shí)體配合研究Hibernate的源代碼是最好的學(xué)習(xí)途徑。兩種技術(shù)側(cè)重點(diǎn)不一樣,因此不能以同一種方式和經(jīng)驗(yàn)硬套。

    楊過(guò)其實(shí)是一個(gè)天資甚高的家伙,從他后期能夠自創(chuàng)武功就能看出來(lái)。第一個(gè)從師地方他看不上,選擇了離開(kāi),之后為了爭(zhēng)口氣,在第二家學(xué)藝分外認(rèn)真。出世前,楊過(guò)對(duì)武功就很感興趣,再加上他的聰明、認(rèn)真、又好學(xué),想不成為高手都難。后來(lái)初現(xiàn)武林,也驗(yàn)證了這一點(diǎn),黃藥師就說(shuō)他的境界比同等境界年輕時(shí)的黃藥師提前了10年。這也驗(yàn)證了一個(gè)道理,長(zhǎng)江后浪推前浪,不服老不行。尤其是IT屆,新的東西一輪一輪涌過(guò)來(lái),而我們的年齡一天天增長(zhǎng),精力一天不如一天,精神集中的能力也有所下降,所以得服老。楊過(guò)的經(jīng)歷告訴我們學(xué)東西還是要以興趣為主,沒(méi)有興趣,學(xué)東西其實(shí)是應(yīng)付別人,自己都不知道自己想要什么,對(duì)自己都敷衍了事的人,可能有太大進(jìn)步嗎?還有就是學(xué)生時(shí)代的我們可以任性,因?yàn)橛邪职謰寢尷蠋煂欀覀?,但是一旦進(jìn)入社會(huì),還是要有所收斂,否則禍不遠(yuǎn)矣。人在江湖,什么事情都有可能發(fā)生。如果不收斂我們的個(gè)性,很難在社會(huì),也就是江湖立足,尤其是軟件開(kāi)發(fā),一山還有一山高在這個(gè)領(lǐng)域體現(xiàn)得淋漓盡致,讓很多當(dāng)初自以為高手的人唏噓不已。沒(méi)關(guān)系,收斂狂傲之心,兼收并蓄。像EJB學(xué)習(xí),吸收Spring的優(yōu)點(diǎn),自成EJB3.0體系。讓眾多擁護(hù)者不至于失望。

    張無(wú)忌其實(shí)并沒(méi)有門(mén)派,他的父親也沒(méi)正經(jīng)教過(guò)張無(wú)忌什么武功,只不過(guò)掛了一個(gè)武當(dāng)派底子的虛名而已。再加上命運(yùn)多舛,孩童時(shí)期就看淡了生死,這是同齡時(shí)期的郭靖、楊過(guò)沒(méi)有經(jīng)歷過(guò)的。所以說(shuō)在出世前,張無(wú)忌幾乎沒(méi)有學(xué)過(guò)什么像樣的武功,連自保恐怕都有困難,歷經(jīng)種種磨難后,終于得上天的恩賜,賦予《九陽(yáng)神功》終成一代“隱俠“。以張無(wú)忌作為我們學(xué)生時(shí)代出世前的例子恐怕不適合。李剛老師曾說(shuō)過(guò),武俠小說(shuō)中主人公掉到一個(gè)山洞里,遇到世外高人傳授武功在現(xiàn)實(shí)生活中是不可能的。我要說(shuō)的是,人生的起點(diǎn)可以很低,但是我們不應(yīng)該自己貶低自己,自我放棄。你看張無(wú)忌中了玄冥神掌,他可有一刻想到要自殺,他總是積極地面對(duì)人生,上天給了我什么,我就享受什么。人生的起點(diǎn)不代表人一生總是在這個(gè)起點(diǎn),只能說(shuō)自己絕不能放棄自己,認(rèn)為就這樣吧。我的人生就這么樣得了。人生總會(huì)有機(jī)遇的,機(jī)遇總會(huì)出現(xiàn)的,不放棄的人,抓住了機(jī)遇,OK,人生的質(zhì)變就發(fā)生了。往往在低谷期不放棄的人,他的路一般都是走得很遠(yuǎn)。張無(wú)忌后來(lái)的際遇,也是和他小時(shí)候看透人生冷暖、世代炎涼相關(guān)的。所以他格外珍惜對(duì)他好的人,當(dāng)然也老受女人的騙(張無(wú)忌他媽的話全忘了,估計(jì)殷素素九泉之下得說(shuō):“這小子,他媽的,把他媽的臨終遺言全忘了”)。無(wú)論學(xué)習(xí)何種技術(shù),都是一條長(zhǎng)遠(yuǎn)的路,任重而道遠(yuǎn),不應(yīng)該因?yàn)槠瘘c(diǎn)很低就一直持著消極的態(tài)度。搞軟件開(kāi)發(fā)起薪低,告訴自己:“沒(méi)關(guān)系,只要堅(jiān)持學(xué)習(xí),增長(zhǎng)經(jīng)驗(yàn),慢慢會(huì)質(zhì)變的。”英雄不問(wèn)出身,學(xué)歷已經(jīng)逐漸一年年弱化了。

    三位主人公的成功多多少少都和他們出世前的經(jīng)歷相關(guān)。這里所謂的出世,實(shí)際上就是指我們畢業(yè)后剛踏入社會(huì)工作。個(gè)人認(rèn)為,從業(yè)前的修煉因人而異,有人適合從一而終,找對(duì)了一個(gè)方向就一直往其更深層發(fā)展,直到摸透原理。代表人物就是郭靖,中年郭靖對(duì)敵一般都是降龍十八掌了,早年的那些花哨招式都不用了。有些人適合根據(jù)原有技術(shù),挖掘優(yōu)點(diǎn)進(jìn)行改革創(chuàng)新,代表人物就是楊過(guò)的黯然銷(xiāo)魂掌了,集各家之大成,配合自己的的心情,隨心而發(fā),隨意而至。還有些同學(xué)喜歡摸著石頭過(guò)河,公司讓我做什么我就學(xué)什么,典型人物張無(wú)忌。張無(wú)忌學(xué)九陽(yáng)神功是命運(yùn)驅(qū)使,不學(xué)就得死!學(xué)乾坤大挪移是小昭驅(qū)使!學(xué)太極拳、太極劍也是形勢(shì)所迫!硬著頭皮趕上去。這就是工作需要什么,我就學(xué)什么。

    筆者對(duì)楊過(guò)的態(tài)度是仰望,能在原有基礎(chǔ)上有創(chuàng)新的東西,就像現(xiàn)在很多的開(kāi)源項(xiàng)目不都是利用已有的資源解決現(xiàn)有問(wèn)題嗎。集大成而創(chuàng)新,說(shuō)實(shí)話在中國(guó)的項(xiàng)目中不是一件容易的事。

    對(duì)郭靖的態(tài)度是敬畏,能做到郭靖這種將一個(gè)東西用精、用細(xì)、用到原理中去的人真的鳳毛麟角。

    對(duì)張無(wú)忌的態(tài)度則是佩服,形勢(shì)所迫,壓力來(lái)了,硬著頭皮也得頂上去。鍛煉能力的時(shí)候到了。

    出事前的各種修煉其實(shí)是鍛煉自己的思維方式和學(xué)習(xí)習(xí)慣,良好的思維方式加上正確的學(xué)習(xí)習(xí)慣,就是出事前最大的財(cái)富。

    出世前的各位同學(xué)們,你們想好你們要做什么樣的俠客了嗎?

    posted @ 2011-12-29 15:40 不高興 閱讀(274) | 評(píng)論 (1)編輯 收藏
     
    http://www.pcmonkey.cn/viewblog_41.html

    在<a>的Onclick事件里加上一個(gè)return flase就行了

    <a href="javascript:" onclick="ShowImg('002.jpg'); return false;">更換圖片</a>

    posted @ 2011-12-20 18:12 不高興 閱讀(1417) | 評(píng)論 (1)編輯 收藏
     
    http://avar.iteye.com/blog/163767

    在做遠(yuǎn)程調(diào)試時(shí),在windows系統(tǒng)和非windows系統(tǒng)下的配置,Tomcat中會(huì)有所差別,具體如下: 

    第一步、配置tomcat 
    一、在windows系統(tǒng)中: 
    打開(kāi)%CATALINE_HOME%/bin下的文件catalina.bat,加入下面這行: 
    set CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8787 

    其中address=8787是沒(méi)被使用的端口號(hào)。連接方式有兩種,為dt_shmem和dt_socket,分別表示本機(jī)調(diào)試和遠(yuǎn)程調(diào)試。 

    二、在非windows系統(tǒng)中: 
    還需要把% CATALINE_HOME %/bin/startup.sh中的最后一行exec "$PRGDIR"/"$EXECUTABLE" start "$@" 中的start改成jpda start。由于默認(rèn)的端口是8000,所以如果8000端口已有他用的話,還需在catalina.sh文件中設(shè)置:JPDA_ADDRESS=8787。 
    輸入命令sh catalina.sh jpda start就可啟動(dòng)tomcat。 

    第二步、配置eclipse 
    在Eclipse中選擇Run?Debug,在彈出的對(duì)話框中右擊Remote Java Application新建一個(gè)遠(yuǎn)程調(diào)試項(xiàng),如下如所示: 



    在“Name”輸入框中輸入遠(yuǎn)程調(diào)試的名稱(chēng),在“Project”中選擇要調(diào)試的項(xiàng)目,在“Host”中輸入需要遠(yuǎn)程調(diào)試項(xiàng)目的IP,也就是tomcat所在的IP,在“Port”中輸入設(shè)置的端口號(hào),比如上面設(shè)置的8787,然后鉤選“Allow termination of remote VM”,點(diǎn)擊“Apply”即可。 
    設(shè)置完后就可以開(kāi)始調(diào)試了,大概分一下幾步: 
    1、啟動(dòng)tomcat(遠(yuǎn)程),如在控制臺(tái)輸出“Listening for transport dt_socket at address: 8787”,即說(shuō)明在tomcat中設(shè)置成功; 
    2、在本機(jī)設(shè)置斷點(diǎn),即在需要監(jiān)視的代碼行前雙擊就會(huì)出現(xiàn)一個(gè)小圓點(diǎn); 
    3、進(jìn)入上圖界面,選擇要調(diào)試的項(xiàng),點(diǎn)擊“Debug”即可進(jìn)行遠(yuǎn)程調(diào)試; 
    4、當(dāng)運(yùn)行到設(shè)置了斷點(diǎn)的代碼行處即可看到如下圖所示的淺綠條。 


    按鍵操作: 
    1、F5鍵與F6鍵均為單步調(diào)試,F(xiàn)5是進(jìn)入本行代碼中執(zhí)行,F(xiàn)6是執(zhí)行本行代碼,跳到下一行; 
    2、F7是跳出函數(shù); 
    3、F8是執(zhí)行到最后。 


    當(dāng)然,為了方便,可以新建一個(gè)批處理文件,假如取名為debug.bat,在這個(gè)文件中加入下面幾行: 

    cd %CATALINE_HOME%/bin 
    set JPDA_ADDRESS=8787 
    set JPDA_TRANSPORT=dt_socket 
    set CATALINA_OPTS=-server -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8787 
    startup 

    這樣需要遠(yuǎn)程調(diào)試時(shí),運(yùn)行debug.bat即可;不需要遠(yuǎn)程調(diào)試時(shí),還是運(yùn)行startup.bat文件。 
    posted @ 2011-12-08 18:08 不高興 閱讀(548) | 評(píng)論 (1)編輯 收藏
     

    posted @ 2011-12-08 11:11 不高興 閱讀(375) | 評(píng)論 (1)編輯 收藏
     
         摘要: http://hain.iteye.com/blog/150875web.xml文件中配置<mime-mapping>下載文件類(lèi)型TOMCAT在默認(rèn)情況下下載.rar的文件是把文件當(dāng)作text打開(kāi),以至于IE打開(kāi)RAR文件為亂碼,如果遇到這種情況時(shí)不必認(rèn)為是瀏覽器的問(wèn)題,大多數(shù)瀏覽器應(yīng)該不會(huì)死皮賴(lài)臉地把二進(jìn)制文件當(dāng)作文本打開(kāi),一般都是服務(wù)器給什么瀏覽器就開(kāi)什么.解決方法: &...  閱讀全文
    posted @ 2011-12-06 18:53 不高興 閱讀(829) | 評(píng)論 (1)編輯 收藏
     
    Copyright © 不高興 Powered by: 博客園 模板提供:滬江博客
    主站蜘蛛池模板: 亚洲最大在线观看| 亚洲精品成人久久久| 久久久久亚洲精品天堂| 韩日电影在线播放免费版| 亚洲第一区精品日韩在线播放| 久久亚洲精品11p| 国产免费av片在线播放 | 亚洲综合色丁香婷婷六月图片| 国产免费AV片在线观看播放| 亚洲国产综合精品中文字幕| 特级做a爰片毛片免费看| 亚洲精品WWW久久久久久| 一级黄色免费网站| 美女被免费网站视频在线| 国产成人免费网站在线观看| 国产亚洲精品成人久久网站| 亚洲情侣偷拍精品| 三年片在线观看免费| 色播在线永久免费视频| 最新亚洲人成网站在线观看| mm1313亚洲国产精品美女| 国产免费一区二区三区免费视频| 国产av无码专区亚洲av桃花庵| 未满十八18禁止免费无码网站| 亚洲成a人不卡在线观看| 成人免费无码大片A毛片抽搐色欲| 亚洲AV无码成人精品区狼人影院 | 又粗又大又硬又爽的免费视频| 一级特黄a大片免费| 久久久久久亚洲av成人无码国产| 真人做人试看60分钟免费视频| 亚洲欧美日本韩国| 亚洲色婷婷综合久久| 永久免费视频网站在线观看| 亚洲成a人片在线不卡一二三区| 亚洲色图综合在线| 欧洲精品99毛片免费高清观看| 亚洲精品久久无码av片俺去也| 亚洲色爱图小说专区| 免费无码又黄又爽又刺激| 人体大胆做受免费视频|