日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

提高编程思想

發布時間:2023/12/18 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 提高编程思想 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

虛函數和抽象函數有什么區別

虛函數是有代碼的并明確允許子類去覆蓋,但子類也可不覆蓋,就是說可以直接用,不用重寫 ?
? 抽象函數是沒有代碼,子類繼承后一定要重寫

?

******************************************************************

在一個類中用虛函數: ?
? 是因為在超類中的有實際代碼的方法,但明確允許子類可以作重寫 ?
? 而且當子類重寫后,可以用子類實例超類;如果這樣,超類變量調用虛函數時,執行的是子類的方法 ?
? ?
? 在一個類中用抽象函數 ?
? 是在寫超類時不確定函數的代碼,讓子類去實現???
???

?

******************************************************************

抽象函數沒有方法體。

c+=4和c=c+4的區別。

String abc="aaa";
char c=abc.charAt(i);
c+=4;

以上如果把:c+=4; 改成:c=c+4;
就不正確。


********************************************************************************************************************************************************************************************************************************************************************

c+=4;
c=c+4;

當c是int類型的時候,這兩個表達式是一樣的,但是c不是int時,這兩個表達式是不一樣的。

這兩個表達式都被稱為賦值表達式。第二條語句使用的是簡單賦值操作符(=),而第一條語句使用的是復合賦值操作符。(復合賦值操作符包括 +=、-=、*=、/=、%=、<<=、>>=、>>>=、&=、^=和|=)Java語言規范中講到,復合賦值 E1 op= E2等價于簡單賦值E1 = (T)((E1)op(E2)),其中T是E1的類型,除非E1只被計算一次。
換句話說,復合賦值表達式自動地將它們所執行的計算的結果轉型為其左側變量的類型。


所以要讓c=c+4 編譯能通過,得
int a=c;
c=(char)(a+4);

System.out.println(c);



字符,字節和編碼

[原創文章,轉載請保留或注明出處:http://www.regexlab.com/zh/encoding.htm]

級別:初級

摘要:本文介紹了字符與編碼的發展過程,相關概念的正確理解。舉例說明了一些實際應用中,編碼的實現方法。然后,本文講述了通常對字符與編碼的幾種誤解,由于這些誤解而導致亂碼產生的原因,以及消除亂碼的辦法。本文的內容涵蓋了“中文問題”,“亂碼問題”。

引言

“字符與編碼”是一個被經常討論的話題。即使這樣,時常出現的亂碼仍然困擾著大家。雖然我們有很多的辦法可以用來消除亂碼,但我們并不一定理解這些辦法的內在原理。而有的亂碼產生的原因,實際上由于底層代碼本身有問題所導致的。因此,不僅是初學者會對字符編碼感到模糊,有的底層開發人員同樣對字符編碼缺乏準確的理解。

回頁首

1. 編碼問題的由來,相關概念的理解

1.1 字符與編碼的發展

從計算機對多國語言的支持角度看,大致可以分為三個階段:

?系統內碼說明系統
階段一ASCII計算機剛開始只支持英語,其它語言不能夠在計算機上存儲和顯示。英文 DOS
階段二ANSI編碼
(本地化)
為使計算機支持更多語言,通常使用 0x80~0xFF 范圍的 2 個字節來表示 1 個字符。比如:漢字 '中' 在中文操作系統中,使用 [0xD6,0xD0] 這兩個字節存儲。

不同的國家和地區制定了不同的標準,由此產生了 GB2312, BIG5, JIS 等各自的編碼標準。這些使用 2 個字節來代表一個字符的各種漢字延伸編碼方式,稱為 ANSI 編碼。在簡體中文系統下,ANSI 編碼代表 GB2312 編碼,在日文操作系統下,ANSI 編碼代表 JIS 編碼。

不同 ANSI 編碼之間互不兼容,當信息在國際間交流時,無法將屬于兩種語言的文字,存儲在同一段 ANSI 編碼的文本中。
中文 DOS,中文 Windows 95/98,日文 Windows 95/98
階段三UNICODE
(國際化)
為了使國際間信息交流更加方便,國際組織制定了 UNICODE 字符集,為各種語言中的每一個字符設定了統一并且唯一的數字編號,以滿足跨語言、跨平臺進行文本轉換、處理的要求。Windows NT/2000/XP,Linux,Java

字符串在內存中的存放方法:

在 ASCII 階段,單字節字符串使用一個字節存放一個字符(SBCS)。比如,"Bob123" 在內存中為:

426F6231323300
Bob123/0

在使用 ANSI 編碼支持多種語言階段,每個字符使用一個字節或多個字節來表示(MBCS),因此,這種方式存放的字符也被稱作多字節字符。比如,"中文123" 在中文 Windows 95 內存中為7個字節,每個漢字占2個字節,每個英文和數字字符占1個字節:

D6D0CEC431323300
123/0

在 UNICODE 被采用之后,計算機存放字符串時,改為存放每個字符在 UNICODE 字符集中的序號。目前計算機一般使用 2 個字節(16 位)來存放一個序號(DBCS),因此,這種方式存放的字符也被稱作寬字節字符。比如,字符串 "中文123" 在 Windows 2000 下,內存中實際存放的是 5 個序號:

2D4E87653100320033000000???? ← 在 x86 CPU 中,低字節在前
123/0?

一共占 10 個字節。

回頁首

1.2 字符,字節,字符串

理解編碼的關鍵,是要把字符的概念和字節的概念理解準確。這兩個概念容易混淆,我們在此做一下區分:

?概念描述舉例
字符人們使用的記號,抽象意義上的一個符號。'1', '中', 'a', '$', '¥', ……
字節計算機中存儲數據的單元,一個8位的二進制數,是一個很具體的存儲空間。0x01, 0x45, 0xFA, ……
ANSI
字符串
在內存中,如果“字符”是以 ANSI 編碼形式存在的,一個字符可能使用一個字節或多個字節來表示,那么我們稱這種字符串為ANSI 字符串或者多字節字符串"中文123"
(占7字節)
UNICODE
字符串
在內存中,如果“字符”是以在 UNICODE 中的序號存在的,那么我們稱這種字符串為 UNICODE 字符串或者寬字節字符串L"中文123"
(占10字節)

由于不同 ANSI 編碼所規定的標準是不相同的,因此,對于一個給定的多字節字符串,我們必須知道它采用的是哪一種編碼規則,才能夠知道它包含了哪些“字符”。而對于UNICODE 字符串來說,不管在什么環境下,它所代表的“字符”內容總是不變的。

回頁首

1.3 字符集與編碼

各個國家和地區所制定的不同 ANSI 編碼標準中,都只規定了各自語言所需的“字符”。比如:漢字標準(GB2312)中沒有規定韓國語字符怎樣存儲。這些 ANSI 編碼標準所規定的內容包含兩層含義:

  • 使用哪些字符。也就是說哪些漢字,字母和符號會被收入標準中。所包含“字符”的集合就叫做“字符集”。
  • 規定每個“字符”分別用一個字節還是多個字節存儲,用哪些字節來存儲,這個規定就叫做“編碼”。
  • 各個國家和地區在制定編碼標準的時候,“字符的集合”和“編碼”一般都是同時制定的。因此,平常我們所說的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”這層含義外,同時也包含了“編碼”的含義。

    UNICODE 字符集”包含了各種語言中使用到的所有“字符”。用來給 UNICODE 字符集編碼的標準有很多種,比如:UTF-8, UTF-7, UTF-16, UnicodeLittle, UnicodeBig 等。

    回頁首

    2. 字符與編碼在程序中的實現

    2.1 程序中的字符與字節

    在 C++ 和 Java 中,用來代表“字符”和“字節”的數據類型,以及進行編碼的方法:

    類型或操作C++Java
    字符wchar_tchar *
    字節charbyte
    ANSI 字符串char[]byte[]
    UNICODE 字符串wchar_t[]String
    字節串→字符串mbstowcs(), MultiByteToWideChar() *string = new String(bytes, "encoding")
    字符串→字節串wcstombs(), WideCharToMultiByte()bytes = string.getBytes("encoding")

    以上需要注意幾點:

  • Java 中的 char 代表一個“UNICODE 字符(寬字節字符)”,而 C++ 中的 char 代表一個字節。
  • MultiByteToWideChar() 和 WideCharToMultiByte() 是 Windows API 函數。
  • 回頁首

    2.2 C++ 中相關實現方法

    聲明一段字符串常量:

    // ANSI 字符串,內容長度 7 字節
    char
    ???? sz[20] ="中文123";

    // UNICODE 字符串,內容長度 5 個 wchar_t(10 字節)
    wchar_t wsz[20] = L"/x4E2D/x6587/x0031/x0032/x0033";

    UNICODE 字符串的 I/O 操作,字符與字節的轉換操作:

    // 運行時設定當前 ANSI 編碼,VC 格式
    setlocale(LC_ALL, ".936");

    // GCC 中格式
    setlocale(LC_ALL, "zh_CN.GBK");

    // Visual C++ 中使用小寫 %s,按照 setlocale 指定編碼輸出到文件
    // GCC 中使用大寫 %S

    fwprintf(fp, L"%s/n", wsz);

    // 把 UNICODE 字符串按照 setlocale 指定的編碼轉換成字節
    wcstombs(sz, wsz, 20);
    // 把字節串按照 setlocale 指定的編碼轉換成 UNICODE 字符串
    mbstowcs(wsz, sz, 20);

    在 Visual C++ 中,UNICODE 字符串常量有更簡單的表示方法。如果源程序的編碼與當前默認 ANSI 編碼不符,則需要使用 #pragma setlocale,告訴編譯器源程序使用的編碼:

    // 如果源程序的編碼與當前默認 ANSI 編碼不一致,
    // 則需要此行,編譯時用來指明當前源程序使用的編碼

    #pragma setlocale
    (".936")

    // UNICODE 字符串常量,內容長度 10 字節
    wchar_t wsz[20] = L"中文123";

    以上需要注意 #pragma setlocale 與 setlocale(LC_ALL, "") 的作用是不同的,#pragma setlocale 在編譯時起作用,setlocale() 在運行時起作用。

    回頁首

    2.3 Java 中相關實現方法

    字符串類 String 中的內容是 UNICODE 字符串:

    // Java 代碼,直接寫中文
    String
    string = "中文123";

    // 得到長度為 5,因為是 5 個字符
    System.out.println(string.length());

    字符串 I/O 操作,字符與字節轉換操作。在 Java 包 java.io.* 中,以“Stream”結尾的類一般是用來操作“字節串”的類,以“Reader”,“Writer”結尾的類一般是用來操作“字符串”的類。

    // 字符串與字節串間相互轉化

    // 按照 GB2312 得到字節(得到多字節字符串)

    byte
    [] bytes = string.getBytes("GB2312");

    // 從字節按照 GB2312 得到 UNICODE 字符串
    string = new String(bytes, "GB2312");

    // 要將 String 按照某種編碼寫入文本文件,有兩種方法:

    // 第一種辦法:用 Stream 類寫入已經按照指定編碼轉化好的字節串

    OutputStream os = new FileOutputStream("1.txt");
    os.write(bytes);
    os.close();

    // 第二種辦法:構造指定編碼的 Writer 來寫入字符串
    Writer ow = new OutputStreamWriter(new FileOutputStream("2.txt"),"GB2312");
    ow.write(string);
    ow.close();

    /* 最后得到的 1.txt 和 2.txt 都是 7 個字節 */

    如果 java 的源程序編碼與當前默認 ANSI 編碼不符,則在編譯的時候,需要指明一下源程序的編碼。比如:

    E:/>javac -encoding BIG5 Hello.java

    以上需要注意區分源程序的編碼與 I/O 操作的編碼,前者是在編譯時起作用,后者是在運行時起作用。

    回頁首

    3. 幾種誤解,以及亂碼產生的原因和解決辦法

    3.1 容易產生的誤解
    ?對編碼的誤解
    誤解一在將“字節串”轉化成“UNICODE 字符串”時,比如在讀取文本文件時,或者通過網絡傳輸文本時,容易將“字節串”簡單地作為單字節字符串,采用每“一個字節”就是“一個字符”的方法進行轉化。

    而實際上,在非英文的環境中,應該將“字節串”作為 ANSI 字符串,采用適當的編碼來得到 UNICODE 字符串,有可能“多個字節”才能得到“一個字符”。

    通常,一直在英文環境下做開發的程序員們,容易有這種誤解。
    誤解二在 DOS,Windows 98 等非 UNICODE 環境下,字符串都是以 ANSI 編碼的字節形式存在的。這種以字節形式存在的字符串,必須知道是哪種編碼才能被正確地使用。這使我們形成了一個慣性思維:“字符串的編碼”。

    當 UNICODE 被支持后,Java 中的 String 是以字符的“序號”來存儲的,不是以“某種編碼的字節”來存儲的,因此已經不存在“字符串的編碼”這個概念了。只有在“字符串”與“字節串”轉化時,或者,將一個“字節串”當成一個 ANSI 字符串時,才有編碼的概念。

    不少的人都有這個誤解。

    第一種誤解,往往是導致亂碼產生的原因。第二種誤解,往往導致本來容易糾正的亂碼問題變得更復雜。

    回頁首

    3.2 常用的編碼簡介

    簡單介紹一下常用的編碼規則,為后邊的章節做一個準備。在這里,我們根據編碼規則的特點,把所有的編碼分成三類:

    分類編碼標準說明
    單字節字符編碼ISO-8859-1最簡單的編碼規則,每一個字節直接作為一個 UNICODE 字符。比如,[0xD6, 0xD0] 這兩個字節,通過 iso-8859-1 轉化為字符串時,將直接得到 [0x00D6, 0x00D0] 兩個 UNICODE 字符,即 "?D"。

    反之,將 UNICODE 字符串通過 iso-8859-1 轉化為字節串時,只能正常轉化 0~255 范圍的字符。
    ANSI 編碼GB2312,
    BIG5,
    Shift_JIS,
    ISO-8859-2 ……
    把 UNICODE 字符串通過 ANSI 編碼轉化為“字節串”時,根據各自編碼的規定,一個 UNICODE 字符可能轉化成一個字節或多個字節。

    反之,將字節串轉化成字符串時,也可能多個字節轉化成一個字符。比如,[0xD6, 0xD0] 這兩個字節,通過 GB2312 轉化為字符串時,將得到 [0x4E2D] 一個字符,即 '中' 字。

    “ANSI 編碼”的特點:
    1. 這些“ANSI 編碼標準”都只能處理各自語言范圍之內的 UNICODE 字符。
    2. “UNICODE 字符”與“轉換出來的字節”之間的關系是人為規定的。
    UNICODE 編碼UTF-8,
    UTF-16, UnicodeBig ……
    與“ANSI 編碼”類似的,把字符串通過 UNICODE 編碼轉化成“字節串”時,一個 UNICODE 字符可能轉化成一個字節或多個字節。

    與“ANSI 編碼”不同的是:
    1. 這些“UNICODE 編碼”能夠處理所有的 UNICODE 字符。
    2. “UNICODE 字符”與“轉換出來的字節”之間是可以通過計算得到的。

    在這里,我們可以看到,前面所講的“誤解一”,即采用每“一個字節”就是“一個字符”的轉化方法,實際上也就等同于采用 iso-8859-1 進行轉化。因此,我們常常使用 bytes = string.getBytes("iso-8859-1") 來進行逆向操作,得到原始的“字節串”。然后再使用正確的 ANSI 編碼,比如 string = new String(bytes, "GB2312"),來得到正確的“UNICODE 字符串”。

    回頁首

    3.3 非 UNICODE 程序在不同語言環境間移植時的亂碼

    非 UNICODE 程序中的字符串,都是以某種 ANSI 編碼形式存在的。如果程序運行時的語言環境與開發時的語言環境不同,將會導致 ANSI 字符串的顯示失敗。

    比如,在日文環境下開發的非 UNICODE 的日文程序界面,拿到中文環境下運行時,界面上將顯示亂碼。如果這個日文程序界面改為采用 UNICODE 來記錄字符串,那么當在中文環境下運行時,界面上將可以顯示正常的日文。

    由于客觀原因,有時候我們必須在中文操作系統下運行非 UNICODE 的日文軟件,這時我們可以采用一些工具,比如,南極星,AppLocale 等,暫時的模擬不同的語言環境。

    回頁首

    3.4 網頁提交字符串

    當頁面中的表單提交字符串時,首先把字符串按照當前頁面的編碼,轉化成字節串。然后再將每個字節轉化成 "%XX" 的格式提交到 Web 服務器。比如,一個編碼為 GB2312 的頁面,提交 "中" 這個字符串時,提交給服務器的內容為 "%D6%D0"。

    在服務器端,Web 服務器把收到的 "%D6%D0" 轉化成 [0xD6, 0xD0] 兩個字節,然后再根據 GB2312 編碼規則得到 "中" 字。

    在 Tomcat 服務器中,request.getParameter() 得到亂碼時,常常是因為前面提到的“誤解一”造成的。默認情況下,當提交 "%D6%D0" 給 Tomcat 服務器時,request.getParameter() 將返回 [0x00D6, 0x00D0] 兩個 UNICODE 字符,而不是返回一個 "中" 字符。因此,我們需要使用 bytes = string.getBytes("iso-8859-1") 得到原始的字節串,再用 string = new String(bytes, "GB2312") 重新得到正確的字符串 "中"。

    回頁首

    3.5 從數據庫讀取字符串

    通過數據庫客戶端(比如 ODBC 或 JDBC)從數據庫服務器中讀取字符串時,客戶端需要從服務器獲知所使用的 ANSI 編碼。當數據庫服務器發送字節流給客戶端時,客戶端負責將字節流按照正確的編碼轉化成 UNICODE 字符串。

    如果從數據庫讀取字符串時得到亂碼,而數據庫中存放的數據又是正確的,那么往往還是因為前面提到的“誤解一”造成的。解決的辦法還是通過 string = new String( string.getBytes("iso-8859-1"), "GB2312") 的方法,重新得到原始的字節串,再重新使用正確的編碼轉化成字符串。

    回頁首

    3.6 電子郵件中的字符串

    當一段 Text 或者 HTML 通過電子郵件傳送時,發送的內容首先通過一種指定的字符編碼轉化成“字節串”,然后再把“字節串”通過一種指定的傳輸編碼(Content-Transfer-Encoding)進行轉化得到另一串“字節串”。比如,打開一封電子郵件源代碼,可以看到類似的內容:

    Content-Type: text/plain;
    ??????? charset="gb2312"
    Content-Transfer-Encoding: base64

    sbG+qcrQuqO17cf4yee74bGjz9W7+b3wudzA7dbQ0MQNCg0KvPKzxqO6uqO17cnnsaPW0NDEDQoNCg==

    最常用的 Content-Transfer-Encoding 有 Base64 和 Quoted-Printable 兩種。在對二進制文件或者中文文本進行轉化時,Base64 得到的“字節串”比 Quoted-Printable 更短。在對英文文本進行轉化時,Quoted-Printable 得到的“字節串”比 Base64 更短。

    郵件的標題,用了一種更簡短的格式來標注“字符編碼”和“傳輸編碼”。比如,標題內容為 "中",則在郵件源代碼中表示為:

    // 正確的標題格式
    Subject: =?GB2312?B?1tA=?=

    其中,

    • 第一個“=?”與“?”中間的部分指定了字符編碼,在這個例子中指定的是 GB2312。
    • “?”與“?”中間的“B”代表 Base64。如果是“Q”則代表 Quoted-Printable。
    • 最后“?”與“?=”之間的部分,就是經過 GB2312 轉化成字節串,再經過 Base64 轉化后的標題內容。

    如果“傳輸編碼”改為 Quoted-Printable,同樣,如果標題內容為 "中":

    // 正確的標題格式
    Subject: =?GB2312?Q?=D6=D0?=

    如果閱讀郵件時出現亂碼,一般是因為“字符編碼”或“傳輸編碼”指定有誤,或者是沒有指定。比如,有的發郵件組件在發送郵件時,標題 "中":

    // 錯誤的標題格式
    Subject: =?ISO-8859-1?Q?=D6=D0?=

    這樣的表示,實際上是明確指明了標題為 [0x00D6, 0x00D0],即 "?D",而不是 "中"。

    回頁首

    4. 幾種錯誤理解的糾正

    誤解:“ISO-8859-1 是國際編碼?”

    非也。iso-8859-1 只是單字節字符集中最簡單的一種,也就是“字節編號”與“UNICODE 字符編號”一致的那種編碼規則。當我們要把一個“字節串”轉化成“字符串”,而又不知道它是哪一種 ANSI 編碼時,先暫時地把“每一個字節”作為“一個字符”進行轉化,不會造成信息丟失。然后再使用 bytes = string.getBytes("iso-8859-1") 的方法可恢復到原始的字節串。

    誤解:“Java 中,怎樣知道某個字符串的內碼?”

    Java 中,字符串類 java.lang.String 處理的是 UNICODE 字符串,不是 ANSI 字符串。我們只需要把字符串作為“抽象的符號的串”來看待。因此不存在字符串的內碼的問題。


    Java代碼編寫的30條建議

    Java代碼編寫的30條建議

    (1) 類名首字母應該大寫。字段、方法以及對象(句柄)的首字母應小寫。對于所有標識符,其中包含的所有單詞都應緊靠在一起,而且大寫中間單詞的首字母。例如:
    ThisIsAClassName
    thisIsMethodOrFieldName
    若在定義中出現了常數初始化字符,則大寫static final基本類型標識符中的所有字母。這樣便可標志出它們屬于編譯期的常數。
    Java包(Package)屬于一種特殊情況:它們全都是小寫字母,即便中間的單詞亦是如此。對于域名擴展名稱,如com,org,net或者edu等,全部都應小寫(這也是Java 1.1和Java 1.2的區別之一)。

    (2) 為了常規用途而創建一個類時,請采取"經典形式",并包含對下述元素的定義:

    equals()
    hashCode()
    toString()
    clone()(implement Cloneable)
    implement Serializable

    (3) 對于自己創建的每一個類,都考慮置入一個main(),其中包含了用于測試那個類的代碼。為使用一個項目中的類,我們沒必要刪除測試代碼。若進行了任何形式的改動,可方便地返回測試。這些代碼也可作為如何使用類的一個示例使用。

    (4) 應將方法設計成簡要的、功能性單元,用它描述和實現一個不連續的類接口部分。理想情況下,方法應簡明扼要。若長度很大,可考慮通過某種方式將其分割成較短的幾個方法。這樣做也便于類內代碼的重復使用(有些時候,方法必須非常大,但它們仍應只做同樣的一件事情)。

    (5) 設計一個類時,請設身處地為客戶程序員考慮一下(類的使用方法應該是非常明確的)。然后,再設身處地為管理代碼的人考慮一下(預計有可能進行哪些形式的修改,想想用什么方法可把它們變得更簡單)。
    (6) 使類盡可能短小精悍,而且只解決一個特定的問題。下面是對類設計的一些建議:
    ■一個復雜的開關語句:考慮采用"多形"機制
    ■數量眾多的方法涉及到類型差別極大的操作:考慮用幾個類來分別實現
    ■許多成員變量在特征上有很大的差別:考慮使用幾個類

    (7) 讓一切東西都盡可能地"私有"--private。可使庫的某一部分"公共化"(一個方法、類或者一個字段等等),就永遠不能把它拿出。若強行拿出,就可能破壞其他人現有的代碼,使他們不得不重新編寫和設計。若只公布自己必須公布的,就可放心大膽地改變其他任何東西。在多線程環境中,隱私是特別重要的一個因素--只有private字段才能在非同步使用的情況下受到保護。

    (8) 謹惕"巨大對象綜合癥"。對一些習慣于順序編程思維、且初涉OOP領域的新手,往往喜歡先寫一個順序執行的程序,再把它嵌入一個或兩個巨大的對象里。根據編程原理,對象表達的應該是應用程序的概念,而非應用程序本身。

    (9) 若不得已進行一些不太雅觀的編程,至少應該把那些代碼置于一個類的內部。

    (10) 任何時候只要發現類與類之間結合得非常緊密,就需要考慮是否采用內部類,從而改善編碼及維護工作(參見第14章14.1.2小節的"用內部類改進代碼")。

    (11) 盡可能細致地加上注釋,并用javadoc注釋文檔語法生成自己的程序文檔。

    (12) 避免使用"魔術數字",這些數字很難與代碼很好地配合。如以后需要修改它,無疑會成為一場噩夢,因為根本不知道"100"到底是指"數組大小"還是"其他全然不同的東西"。所以,我們應創建一個常數,并為其使用具有說服力的描述性名稱,并在整個程序中都采用常數標識符。這樣可使程序更易理解以及更易維護。

    (13) 涉及構建器和異常的時候,通常希望重新丟棄在構建器中捕獲的任何異常--如果它造成了那個對象的創建失敗。這樣一來,調用者就不會以為那個對象已正確地創建,從而盲目地繼續。

    (14) 當客戶程序員用完對象以后,若你的類要求進行任何清除工作,可考慮將清除代碼置于一個良好定義的方法里,采用類似于cleanup()這樣的名字,明確表明自己的用途。除此以外,可在類內放置一個boolean(布爾)標記,指出對象是否已被清除。在類的finalize()方法里,請確定對象已被清除,并已丟棄了從RuntimeException繼承的一個類(如果還沒有的話),從而指出一個編程錯誤。在采取象這樣的方案之前,請確定finalize()能夠在自己的系統中工作(可能需要調用System.runFinalizersOnExit(true),從而確保這一行為)。

    (15) 在一個特定的作用域內,若一個對象必須清除(非由垃圾收集機制處理),請采用下述方法:初始化對象;若成功,則立即進入一個含有finally從句的try塊,開始清除工作。

    (16) 若在初始化過程中需要覆蓋(取消)finalize(),請記住調用super.finalize()(若Object屬于我們的直接超類,則無此必要)。在對finalize()進行覆蓋的過程中,對super.finalize()的調用應屬于最后一個行動,而不應是第一個行動,這樣可確保在需要基礎類組件的時候它們依然有效。

    (17) 創建大小固定的對象集合時,請將它們傳輸至一個數組(若準備從一個方法里返回這個集合,更應如此操作)。這樣一來,我們就可享受到數組在編譯期進行類型檢查的好處。此外,為使用它們,數組的接收者也許并不需要將對象"造型"到數組里。

    (18) 盡量使用interfaces,不要使用abstract類。若已知某樣東西準備成為一個基礎類,那么第一個選擇應是將其變成一個interface(接口)。只有在不得不使用方法定義或者成員變量的時候,才需要將其變成一個abstract(抽象)類。接口主要描述了客戶希望做什么事情,而一個類則致力于(或允許)具體的實施細節。

    (19) 在構建器內部,只進行那些將對象設為正確狀態所需的工作。盡可能地避免調用其他方法,因為那些方法可能被其他人覆蓋或取消,從而在構建過程中產生不可預知的結果(參見第7章的詳細說明)。

    (20) 對象不應只是簡單地容納一些數據;它們的行為也應得到良好的定義。

    (21) 在現成類的基礎上創建新類時,請首先選擇"新建"或"創作"。只有自己的設計要求必須繼承時,才應考慮這方面的問題。若在本來允許新建的場合使用了繼承,則整個設計會變得沒有必要地復雜。

    (22) 用繼承及方法覆蓋來表示行為間的差異,而用字段表示狀態間的區別。一個非常極端的例子是通過對不同類的繼承來表示顏色,這是絕對應該避免的:應直接使用一個"顏色"字段。

    (23) 為避免編程時遇到麻煩,請保證在自己類路徑指到的任何地方,每個名字都僅對應一個類。否則,編譯器可能先找到同名的另一個類,并報告出錯消息。若懷疑自己碰到了類路徑問題,請試試在類路徑的每一個起點,搜索一下同名的.class文件。

    (24) 在Java 1.1 AWT中使用事件"適配器"時,特別容易碰到一個陷阱。若覆蓋了某個適配器方法,同時拼寫方法沒有特別講究,最后的結果就是新添加一個方法,而不是覆蓋現成方法。然而,由于這樣做是完全合法的,所以不會從編譯器或運行期系統獲得任何出錯提示--只不過代碼的工作就變得不正常了。

    (25) 用合理的設計方案消除"偽功能"。也就是說,假若只需要創建類的一個對象,就不要提前限制自己使用應用程序,并加上一條"只生成其中一個"注釋。請考慮將其封裝成一個"獨生子"的形式。若在主程序里有大量散亂的代碼,用于創建自己的對象,請考慮采納一種創造性的方案,將些代碼封裝起來。

    (26) 警惕"分析癱瘓"。請記住,無論如何都要提前了解整個項目的狀況,再去考察其中的細節。由于把握了全局,可快速認識自己未知的一些因素,防止在考察細節的時候陷入"死邏輯"中。

    (27) 警惕"過早優化"。首先讓它運行起來,再考慮變得更快--但只有在自己必須這樣做、而且經證實在某部分代碼中的確存在一個性能瓶頸的時候,才應進行優化。除非用專門的工具分析瓶頸,否則很有可能是在浪費自己的時間。性能提升的隱含代價是自己的代碼變得難于理解,而且難于維護。

    (28) 請記住,閱讀代碼的時間比寫代碼的時間多得多。思路清晰的設計可獲得易于理解的程序,但注釋、細致的解釋以及一些示例往往具有不可估量的價值。無論對你自己,還是對后來的人,它們都是相當重要的。如對此仍有懷疑,那么請試想自己試圖從聯機Java文檔里找出有用信息時碰到的挫折,這樣或許能將你說服。

    (29) 如認為自己已進行了良好的分析、設計或者實施,那么請稍微更換一下思維角度。試試邀請一些外來人士--并不一定是專家,但可以是來自本公司其他部門的人。請他們用完全新鮮的眼光考察你的工作,看看是否能找出你一度熟視無睹的問題。采取這種方式,往往能在最適合修改的階段找出一些關鍵性的問題,避免產品發行后再解決問題而造成的金錢及精力方面的損失。

    (30) 良好的設計能帶來最大的回報。簡言之,對于一個特定的問題,通常會花較長的時間才能找到一種最恰當的解決方案。但一旦找到了正確的方法,以后的工作就輕松多了,再也不用經歷數小時、數天或者數月的痛苦掙扎。我們的努力工作會帶來最大的回報(甚至無可估量)。而且由于自己傾注了大量心血,最終獲得一個出色的設計方案,成功的快感也是令人心動的。堅持抵制草草完工的誘惑--那樣做往往得不償失

    (3) 對于自己創建的每一個類,都考慮置入一個main(),其中包含了用于測試那 個類的代碼。為使用一個項目中的類,我們沒必要刪除測試代碼。若進行了任 何形式的改動,可方便地返回測試。這些代碼也可作為如何使用類的一個示例 使用。

    this is absolutly bad!

    (4) 應將方法設計成簡要的、功能性單元,用它描述和實現一個不連續的類接 口部分。理想情況下,方法應簡明扼要。若長度很大,可考慮通過某種方式將 其分割成較短的幾個方法。這樣做也便于類內代碼的重復使用(有些時候,方 法必須非常大,但它們仍應只做同樣的一件事情)。

    (5) 設計一個類時,請設身處地為客戶程序員考慮一下(類的使用方法應該是 非常明確的)。然后,再設身處地為管理代碼的人考慮一下(預計有可能進行 哪些形式的修改,想想用什么方法可把它們變得更簡單)。
    (6) 使類盡可能短小精悍,而且只解決一個特定的問題。下面是對類設計的一 些建議:
    ■一個復雜的開關語句:考慮采用"多形"機制
    ■數量眾多的方法涉及到類型差別極大的操作:考慮用幾個類來分別實現
    ■許多成員變量在特征上有很大的差別:考慮使用幾個類

    (7) 讓一切東西都盡可能地"私有"--private。可使庫的某一部分"公共化"(一個 方法、類或者一個字段等等),就永遠不能把它拿出。若強行拿出,就可能破 壞其他人現有的代碼,使他們不得不重新編寫和設計。若只公布自己必須公布 的,就可放心大膽地改變其他任何東西。在多線程環境中,隱私是特別重要的 一個因素--只有private字段才能在非同步使用的情況下受到保護。

    not necessary , pretotect or package level also fine in most case

    (8) 謹惕"巨大對象綜合癥"。對一些習慣于順序編程思維、且初涉OOP領域的新 手,往往喜歡先寫一個順序執行的程序,再把它嵌入一個或兩個巨大的對象 里。根據編程原理,對象表達的應該是應用程序的概念,而非應用程序本身。

    (9) 若不得已進行一些不太雅觀的編程,至少應該把那些代碼置于一個類的內 部。

    (10) 任何時候只要發現類與類之間結合得非常緊密,就需要考慮是否采用內部 類,從而改善編碼及維護工作(參見第14章14.1.2小節的"用內部類改進代 碼")。

    (11) 盡可能細致地加上注釋,并用javadoc注釋文檔語法生成自己的程序文檔。

    (12) 避免使用"魔術數字",這些數字很難與代碼很好地配合。如以后需要修改 它,無疑會成為一場噩夢,因為根本不知道"100"到底是指"數組大小"還是"其 他全然不同的東西"。所以,我們應創建一個常數,并為其使用具有說服力的描 述性名稱,并在整個程序中都采用常數標識符。這樣可使程序更易理解以及更 易維護。

    (13) 涉及構建器和異常的時候,通常希望重新丟棄在構建器中捕獲的任何異常- -如果它造成了那個對象的創建失敗。這樣一來,調用者就不會以為那個對象已 正確地創建,從而盲目地繼續。

    (14) 當客戶程序員用完對象以后,若你的類要求進行任何清除工作,可考慮將 清除代碼置于一個良好定義的方法里,采用類似于cleanup()這樣的名字,明確 表明自己的用途。除此以外,可在類內放置一個boolean(布爾)標記,指出 對象是否已被清除。在類的finalize()方法里,請確定對象已被清除,并已丟棄 了從RuntimeException繼承的一個類(如果還沒有的話),從而指出一個編程 錯誤。在采取象這樣的方案之前,請確定finalize()能夠在自己的系統中工作 (可能需要調用System.runFinalizersOnExit(true),從而確保這一行為)。

    (15) 在一個特定的作用域內,若一個對象必須清除(非由垃圾收集機制處 理),請采用下述方法:初始化對象;若成功,則立即進入一個含有finally從 句的try塊,開始清除工作。

    (16) 若在初始化過程中需要覆蓋(取消)finalize(),請記住調用 super.finalize()(若Object屬于我們的直接超類,則無此必要)。在對finalize() 進行覆蓋的過程中,對super.finalize()的調用應屬于最后一個行動,而不應是第 一個行動,這樣可確保在需要基礎類組件的時候它們依然有效。

    (17) 創建大小固定的對象集合時,請將它們傳輸至一個數組(若準備從一個方 法里返回這個集合,更應如此操作)。這樣一來,我們就可享受到數組在編譯 期進行類型檢查的好處。此外,為使用它們,數組的接收者也許并不需要將對 象"造型"到數組里。

    (18) 盡量使用interfaces,不要使用abstract類。若已知某樣東西準備成為一個 基礎類,那么第一個選擇應是將其變成一個interface(接口)。只有在不得不 使用方法定義或者成員變量的時候,才需要將其變成一個abstract(抽象) 類。接口主要描述了客戶希望做什么事情,而一個類則致力于(或允許)具體 的實施細節。

    they are total diffrent ,

    (19) 在構建器內部,只進行那些將對象設為正確狀態所需的工作。盡可能地避 免調用其他方法,因為那些方法可能被其他人覆蓋或取消,從而在構建過程中 產生不可預知的結果(參見第7章的詳細說明)。

    (20) 對象不應只是簡單地容納一些數據;它們的行為也應得到良好的定義。

    (21) 在現成類的基礎上創建新類時,請首先選擇"新建"或"創作"。只有自己的設 計要求必須繼承時,才應考慮這方面的問題。若在本來允許新建的場合使用了 繼承,則整個設計會變得沒有必要地復雜。

    (22) 用繼承及方法覆蓋來表示行為間的差異,而用字段表示狀態間的區別。一 個非常極端的例子是通過對不同類的繼承來表示顏色,這是絕對應該避免的: 應直接使用一個"顏色"字段。

    (23) 為避免編程時遇到麻煩,請保證在自己類路徑指到的任何地方,每個名字 都僅對應一個類。否則,編譯器可能先找到同名的另一個類,并報告出錯消 息。若懷疑自己碰到了類路徑問題,請試試在類路徑的每一個起點,搜索一下 同名的.class文件。

    classpath is not that simple

    (24) 在Java 1.1 AWT中使用事件"適配器"時,特別容易碰到一個陷阱。若覆蓋了 某個適配器方法,同時拼寫方法沒有特別講究,最后的結果就是新添加一個方 法,而不是覆蓋現成方法。然而,由于這樣做是完全合法的,所以不會從編譯 器或運行期系統獲得任何出錯提示--只不過代碼的工作就變得不正常了。

    (25) 用合理的設計方案消除"偽功能"。也就是說,假若只需要創建類的一個對 象,就不要提前限制自己使用應用程序,并加上一條"只生成其中一個"注釋。 請考慮將其封裝成一個"獨生子"的形式。若在主程序里有大量散亂的代碼,用 于創建自己的對象,請考慮采納一種創造性的方案,將些代碼封裝起來。

    (26) 警惕"分析癱瘓"。請記住,無論如何都要提前了解整個項目的狀況,再去 考察其中的細節。由于把握了全局,可快速認識自己未知的一些因素,防止在 考察細節的時候陷入"死邏輯"中。

    (27) 警惕"過早優化"。首先讓它運行起來,再考慮變得更快--但只有在自己必須 這樣做、而且經證實在某部分代碼中的確存在一個性能瓶頸的時候,才應進行 優化。除非用專門的工具分析瓶頸,否則很有可能是在浪費自己的時間。性能 提升的隱含代價是自己的代碼變得難于理解,而且難于維護。

    but know early and design better at first is always necesary, or else
    you die

    (28) 請記住,閱讀代碼的時間比寫代碼的時間多得多。思路清晰的設計可獲得 易于理解的程序,但注釋、細致的解釋以及一些示例往往具有不可估量的價 值。無論對你自己,還是對后來的人,它們都是相當重要的。如對此仍有懷 疑,那么請試想自己試圖從聯機Java文檔里找出有用信息時碰到的挫折,這樣 或許能將你說服。

    (29) 如認為自己已進行了良好的分析、設計或者實施,那么請稍微更換一下思 維角度。試試邀請一些外來人士--并不一定是專家,但可以是來自本公司其他 部門的人。請他們用完全新鮮的眼光考察你的工作,看看是否能找出你一度熟 視無睹的問題。采取這種方式,往往能在最適合修改的階段找出一些關鍵性的 問題,避免產品發行后再解決問題而造成的金錢及精力方面的損失。

    (30) 良好的設計能帶來最大的回報。簡言之,對于一個特定的問題,通常會花 較長的時間才能找到一種最恰當的解決方案。但一旦找到了正確的方法,以后 的工作就輕松多了,再也不用經歷數小時、數天或者數月的痛苦掙扎。我們的 努力工作會帶來最大的回報(甚至無可估量)。而且由于自己傾注了大量心 血,最終獲得一個出色的設計方案,成功的快感也是令人心動的。堅持抵制草 草完工的誘惑--那樣做往往得不償失


    Java性能優化技巧集錦

    Java性能優化技巧集錦

    一、通用篇
      1.1 不用new關鍵詞創建類的實例
      1.2 使用非阻塞I/O
      1.3 慎用異常
      1.4 不要重復初始化變量
      1.5 盡量指定類的final修飾符
      1.6 盡量使用局部變量
      1.7 乘法和除法
    二、J2EE篇
      2.1 使用緩沖標記
      2.2 始終通過會話Bean訪問實體Bean
      2.3 選擇合適的引用機制
      2.4 在部署描述器中設置只讀屬性
      2.5 緩沖對EJB Home的訪問
      2.6 為EJB實現本地接口
      2.7 生成主鍵
      2.8 及時清除不再需要的會話
      2.9 在JSP頁面中關閉無用的會話
      2.10 Servlet與內存使用
      2.11 HTTP Keep-Alive
      2.12 JDBC與Unicode
      2.13 JDBC與I/O
      1.14 內存數據庫
    三、GUI篇
      3.1 用JAR壓縮類文件
      3.2 提示Applet裝入進程
      3.3 在畫出圖形之前預先裝入它
      3.4 覆蓋update方法
      3.5 延遲重畫操作
      3.6 使用雙緩沖區
      3.7 使用BufferedImage
      3.8 使用VolatileImage
      3.9 使用Window Blitting
    四、補充資料
    ===================================
    正文:
    ===================================
    一、通用篇

    “通用篇”討論的問題適合于大多數Java應用。

    1.1 不用new關鍵詞創建類的實例

    用new關鍵詞創建類的實例時,構造函數鏈中的所有構造函數都會被自動調用。但如果一個對象實現了Cloneable接口,我們可以調用它的clone()方法。clone()方法不會調用任何類構造函數。

    在使用設計模式(Design Pattern)的場合,如果用Factory模式創建對象,則改用clone()方法創建新的對象實例非常簡單。例如,下面是Factory模式的一個典型實現:


    public static Credit getNewCredit() {
    return new Credit();
    }

    ?

    改進后的代碼使用clone()方法,如下所示:


    private static Credit BaseCredit = new Credit();
    public static Credit getNewCredit() {
    return (Credit) BaseCredit.clone();
    }

    ?

    上面的思路對于數組處理同樣很有用。

    1.2 使用非阻塞I/O

    版本較低的JDK不支持非阻塞I/O API。為避免I/O阻塞,一些應用采用了創建大量線程的辦法(在較好的情況下,會使用一個緩沖池)。這種技術可以在許多必須支持并發I/O流的應用中見到,如Web服務器、報價和拍賣應用等。然而,創建Java線程需要相當可觀的開銷。

    JDK 1.4引入了非阻塞的I/O庫(java.nio)。如果應用要求使用版本較早的JDK,在這里有一個支持非阻塞I/O的軟件包。

    請參見Sun中國網站的《調整Java的I/O性能》。

    1.3 慎用異常

    異常對性能不利。拋出異常首先要創建一個新的對象。Throwable接口的構造函數調用名為fillInStackTrace()的本地(Native)方法,fillInStackTrace()方法檢查堆棧,收集調用跟蹤信息。只要有異常被拋出,VM就必須調整調用堆棧,因為在處理過程中創建了一個新的對象。

    異常只能用于錯誤處理,不應該用來控制程序流程。

    1.4 不要重復初始化變量

    默認情況下,調用類的構造函數時, Java會把變量初始化成確定的值:所有的對象被設置成null,整數變量(byte、short、int、long)設置成0,float和 double變量設置成0.0,邏輯值設置成false。當一個類從另一個類派生時,這一點尤其應該注意,因為用new關鍵詞創建一個對象時,構造函數鏈中的所有構造函數都會被自動調用。

    1.5 盡量指定類的final修飾符

    帶有final修飾符的類是不可派生的。在Java核心API中,有許多應用final的例子,例如java.lang.String。為String類指定final防止了人們覆蓋length()方法。

    另外,如果指定一個類為final,則該類所有的方法都是final。Java編譯器會尋找機會內聯(inline)所有的final方法(這和具體的編譯器實現有關)。此舉能夠使性能平均提高50%。

    1.6 盡量使用局部變量

    調用方法時傳遞的參數以及在調用中創建的臨時變量都保存在棧(Stack)中,速度較快。其他變量,如靜態變量、實例變量等,都在堆(Heap)中創建,速度較慢。另外,依賴于具體的編譯器/JVM,局部變量還可能得到進一步優化。請參見《盡可能使用堆棧變量》。

    1.7 乘法和除法

    考慮下面的代碼:


    for (val = 0; val < 100000; val +=5) { alterX = val * 8; myResult = val * 2; }

    ?

    用移位操作替代乘法操作可以極大地提高性能。下面是修改后的代碼:


    for (val = 0; val < 100000; val += 5) { alterX = val << 3; myResult = val << 1; }

    ?

    修改后的代碼不再做乘以8的操作,而是改用等價的左移3位操作,每左移1位相當于乘以2。相應地,右移1位操作相當于除以2。值得一提的是,雖然移位操作速度快,但可能使代碼比較難于理解,所以最好加上一些注釋。

    二、J2EE篇

    前面介紹的改善性能技巧適合于大多數Java應用,接下來要討論的問題適合于使用JSP、EJB或JDBC的應用。

    2.1 使用緩沖標記

    一些應用服務器加入了面向JSP的緩沖標記功能。例如,BEA的WebLogic Server從6.0版本開始支持這個功能,Open Symphony工程也同樣支持這個功能。JSP緩沖標記既能夠緩沖頁面片斷,也能夠緩沖整個頁面。當JSP頁面執行時,如果目標片斷已經在緩沖之中,則生成該片斷的代碼就不用再執行。頁面級緩沖捕獲對指定URL的請求,并緩沖整個結果頁面。對于購物籃、目錄以及門戶網站的主頁來說,這個功能極其有用。對于這類應用,頁面級緩沖能夠保存頁面執行的結果,供后繼請求使用。

    對于代碼邏輯復雜的頁面,利用緩沖標記提高性能的效果比較明顯;反之,效果可能略遜一籌。

    請參見《用緩沖技術提高JSP應用的性能和穩定性》。

    2.2 始終通過會話Bean訪問實體Bean

    直接訪問實體Bean不利于性能。當客戶程序遠程訪問實體Bean時,每一個get方法都是一個遠程調用。訪問實體Bean的會話Bean是本地的,能夠把所有數據組織成一個結構,然后返回它的值。

    用會話Bean封裝對實體Bean的訪問能夠改進事務管理,因為會話Bean只有在到達事務邊界時才會提交。每一個對get方法的直接調用產生一個事務,容器將在每一個實體Bean的事務之后執行一個“裝入-讀取”操作。

    一些時候,使用實體Bean會導致程序性能不佳。如果實體Bean的唯一用途就是提取和更新數據,改成在會話Bean之內利用JDBC訪問數據庫可以得到更好的性能。

    2.3 選擇合適的引用機制

    在典型的JSP應用系統中,頁頭、頁腳部分往往被抽取出來,然后根據需要引入頁頭、頁腳。當前,在JSP頁面中引入外部資源的方法主要有兩種:include指令,以及include動作。


    include指令:例如<%@ include file="copyright.html" %>。該指令在編譯時引入指定的資源。在編譯之前,帶有include指令的頁面和指定的資源被合并成一個文件。被引用的外部資源在編譯時就確定,比運行時才確定資源更高效。
    include動作:例如<jsp:include page="copyright.jsp" />。該動作引入指定頁面執行后生成的結果。由于它在運行時完成,因此對輸出結果的控制更加靈活。但時,只有當被引用的內容頻繁地改變時,或者在對主頁面的請求沒有出現之前,被引用的頁面無法確定時,使用include動作才合算。
    2.4 在部署描述器中設置只讀屬性

    實體Bean的部署描述器允許把所有get方法設置成“只讀”。當某個事務單元的工作只包含執行讀取操作的方法時,設置只讀屬性有利于提高性能,因為容器不必再執行存儲操作。

    2.5 緩沖對EJB Home的訪問

    EJB Home接口通過JNDI名稱查找獲得。這個操作需要相當可觀的開銷。JNDI查找最好放入Servlet的init()方法里面。如果應用中多處頻繁地出現EJB訪問,最好創建一個EJBHomeCache類。EJBHomeCache類一般應該作為singleton實現。

    2.6 為EJB實現本地接口

    本地接口是EJB 2.0規范新增的內容,它使得Bean能夠避免遠程調用的開銷。請考慮下面的代碼。


    PayBeanHome home = (PayBeanHome)
    javax.rmi.PortableRemoteObject.narrow
    (ctx.lookup ("PayBeanHome"), PayBeanHome.class);
    PayBean bean = (PayBean)
    javax.rmi.PortableRemoteObject.narrow
    (home.create(), PayBean.class);


    第一個語句表示我們要尋找Bean的Home接口。這個查找通過JNDI進行,它是一個RMI調用。然后,我們定位遠程對象,返回代理引用,這也是一個 RMI調用。第二個語句示范了如何創建一個實例,涉及了創建IIOP請求并在網絡上傳輸請求的stub程序,它也是一個RMI調用。

    要實現本地接口,我們必須作如下修改:


    方法不能再拋出java.rmi.RemoteException異常,包括從RemoteException派生的異常,比如 TransactionRequiredException、TransactionRolledBackException和 NoSuchObjectException。EJB提供了等價的本地異常,如TransactionRequiredLocalException、 TransactionRolledBackLocalException和NoSuchObjectLocalException。
    所有數據和返回值都通過引用的方式傳遞,而不是傳遞值。
    本地接口必須在EJB部署的機器上使用。簡而言之,客戶程序和提供服務的組件必須在同一個JVM上運行。
    如果Bean實現了本地接口,則其引用不可串行化。
    請參見《用本地引用提高EJB訪問效率》。

    2.7 生成主鍵

    在EJB之內生成主鍵有許多途徑,下面分析了幾種常見的辦法以及它們的特點。

    利用數據庫內建的標識機制(SQL Server的IDENTITY或Oracle的SEQUENCE)。這種方法的缺點是EJB可移植性差。

    由實體Bean自己計算主鍵值(比如做增量操作)。它的缺點是要求事務可串行化,而且速度也較慢。

    利用NTP之類的時鐘服務。這要求有面向特定平臺的本地代碼,從而把Bean固定到了特定的OS之上。另外,它還導致了這樣一種可能,即在多CPU的服務器上,同一個毫秒之內生成了兩個主鍵。

    借鑒Microsoft的思路,在Bean中創建一個GUID。然而,如果不求助于JNI,Java不能確定網卡的MAC地址;如果使用JNI,則程序就要依賴于特定的OS。

    還有其他幾種辦法,但這些辦法同樣都有各自的局限。似乎只有一個答案比較理想:結合運用RMI和JNDI。先通過RMI注冊把RMI遠程對象綁定到JNDI樹。客戶程序通過JNDI進行查找。下面是一個例子:


    public class keyGenerator extends UnicastRemoteObject implements Remote {
    private static long KeyValue = System.currentTimeMillis();
    public static synchronized long getKey() throws RemoteException { return KeyValue++; }


    2.8 及時清除不再需要的會話

    為了清除不再活動的會話,許多應用服務器都有默認的會話超時時間,一般為30分鐘。當應用服務器需要保存更多會話時,如果內存容量不足,操作系統會把部分內存數據轉移到磁盤,應用服務器也可能根據“最近最頻繁使用”(Most Recently Used)算法把部分不活躍的會話轉儲到磁盤,甚至可能拋出“內存不足”異常。在大規模系統中,串行化會話的代價是很昂貴的。當會話不再需要時,應當及時調用HttpSession.invalidate()方法清除會話。HttpSession.invalidate()方法通常可以在應用的退出頁面調用。

    2.9 在JSP頁面中關閉無用的會話

    對于那些無需跟蹤會話狀態的頁面,關閉自動創建的會話可以節省一些資源。使用如下page指令:


    <%@ page session="false"%>

    2.10 Servlet與內存使用

    許多開發者隨意地把大量信息保存到用戶會話之中。一些時候,保存在會話中的對象沒有及時地被垃圾回收機制回收。從性能上看,典型的癥狀是用戶感到系統周期性地變慢,卻又不能把原因歸于任何一個具體的組件。如果監視JVM的堆空間,它的表現是內存占用不正常地大起大落。

    解決這類內存問題主要有二種辦法。第一種辦法是,在所有作用范圍為會話的Bean中實現HttpSessionBindingListener接口。這樣,只要實現valueUnbound()方法,就可以顯式地釋放Bean使用的資源。

    另外一種辦法就是盡快地把會話作廢。大多數應用服務器都有設置會話作廢間隔時間的選項。另外,也可以用編程的方式調用會話的 setMaxInactiveInterval()方法,該方法用來設定在作廢會話之前,Servlet容器允許的客戶請求的最大間隔時間,以秒計。

    2.11 HTTP Keep-Alive

    Keep-Alive功能使客戶端到服務器端的連接持續有效,當出現對服務器的后繼請求時,Keep-Alive功能避免了建立或者重新建立連接。市場上的大部分Web服務器,包括iPlanet、IIS和Apache,都支持HTTP Keep-Alive。對于提供靜態內容的網站來說,這個功能通常很有用。但是,對于負擔較重的網站來說,這里存在另外一個問題:雖然為客戶保留打開的連接有一定的好處,但它同樣影響了性能,因為在處理暫停期間,本來可以釋放的資源仍舊被占用。當Web服務器和應用服務器在同一臺機器上運行時,Keep- Alive功能對資源利用的影響尤其突出。

    2.12 JDBC與Unicode

    想必你已經了解一些使用JDBC時提高性能的措施,比如利用連接池、正確地選擇存儲過程和直接執行的SQL、從結果集刪除多余的列、預先編譯SQL語句,等等。

    除了這些顯而易見的選擇之外,另一個提高性能的好選擇可能就是把所有的字符數據都保存為Unicode(代碼頁13488)。Java以Unicode形式處理所有數據,因此,數據庫驅動程序不必再執行轉換過程。但應該記住:如果采用這種方式,數據庫會變得更大,因為每個Unicode字符需要2個字節存儲空間。另外,如果有其他非Unicode的程序訪問數據庫,性能問題仍舊會出現,因為這時數據庫驅動程序仍舊必須執行轉換過程。

    2.13 JDBC與I/O

    如果應用程序需要訪問一個規模很大的數據集,則應當考慮使用塊提取方式。默認情況下,JDBC每次提取32行數據。舉例來說,假設我們要遍歷一個5000 行的記錄集,JDBC必須調用數據庫157次才能提取到全部數據。如果把塊大小改成512,則調用數據庫的次數將減少到10次。

    在一些情形下這種技術無效。例如,如果使用可滾動的記錄集,或者在查詢中指定了FOR UPDATE,則塊操作方式不再有效。

    1.14 內存數據庫

    許多應用需要以用戶為單位在會話對象中保存相當數量的數據,典型的應用如購物籃和目錄等。由于這類數據可以按照行/列的形式組織,因此,許多應用創建了龐大的Vector或HashMap。在會話中保存這類數據極大地限制了應用的可伸縮性,因為服務器擁有的內存至少必須達到每個會話占用的內存數量乘以并發用戶最大數量,它不僅使服務器價格昂貴,而且垃圾收集的時間間隔也可能延長到難以忍受的程度。

    一些人把購物籃/目錄功能轉移到數據庫層,在一定程度上提高了可伸縮性。然而,把這部分功能放到數據庫層也存在問題,且問題的根源與大多數關系數據庫系統的體系結構有關。對于關系數據庫來說,運行時的重要原則之一是確保所有的寫入操作穩定、可靠,因而,所有的性能問題都與物理上把數據寫入磁盤的能力有關。關系數據庫力圖減少I/O操作,特別是對于讀操作,但實現該目標的主要途徑只是執行一套實現緩沖機制的復雜算法,而這正是數據庫層第一號性能瓶頸通常總是 CPU的主要原因。

    一種替代傳統關系數據庫的方案是,使用在內存中運行的數據庫(In-memory Database),例如TimesTen。內存數據庫的出發點是允許數據臨時地寫入,但這些數據不必永久地保存到磁盤上,所有的操作都在內存中進行。這樣,內存數據庫不需要復雜的算法來減少I/O操作,而且可以采用比較簡單的加鎖機制,因而速度很快。

    三、GUI篇

    這一部分介紹的內容適合于圖形用戶界面的應用(Applet和普通應用),要用到AWT或Swing。

    3.1 用JAR壓縮類文件

    Java檔案文件(JAR文件)是根據JavaBean標準壓縮的文件,是發布JavaBean組件的主要方式和推薦方式。JAR檔案有助于減少文件體積,縮短下載時間。例如,它有助于Applet提高啟動速度。一個JAR文件可以包含一個或者多個相關的Bean以及支持文件,比如圖形、聲音、HTML 和其他資源。

    要在HTML/JSP文件中指定JAR文件,只需在Applet標記中加入ARCHIVE = "name.jar"聲明。

    請參見《使用檔案文件提高 applet 的加載速度》。

    3.2 提示Applet裝入進程

    你是否看到過使用Applet的網站,注意到在應該運行Applet的地方出現了一個占位符?當Applet的下載時間較長時,會發生什么事情?最大的可能就是用戶掉頭離去。在這種情況下,顯示一個Applet正在下載的信息無疑有助于鼓勵用戶繼續等待。

    下面我們來看看一種具體的實現方法。首先創建一個很小的Applet,該Applet負責在后臺下載正式的Applet:


    import java.applet.Applet;
    import java.applet.AppletStub;
    import java.awt.Label;
    import java.awt.Graphics;
    import java.awt.GridLayout;
    public class PreLoader extends Applet implements Runnable, AppletStub {
    String largeAppletName;
    Label label;
    public void init() {
    // 要求裝載的正式Applet
    largeAppletName = getParameter("applet");
    // “請稍等”提示信息
    label = new Label("請稍等..." + largeAppletName);
    add(label);
    }
    public void run(){
    try {
    // 獲得待裝載Applet的類
    Class largeAppletClass = Class.forName(largeAppletName);
    // 創建待裝載Applet的實例
    Applet largeApplet = (Applet)largeAppletClass.newInstance();
    // 設置該Applet的Stub程序
    largeApplet.setStub(this);
    // 取消“請稍等”信息
    remove(label);
    // 設置布局
    setLayout(new GridLayout(1, 0));
    add(largeApplet);
    // 顯示正式的Applet
    largeApplet.init();
    largeApplet.start();
    }
    catch (Exception ex) {
    // 顯示錯誤信息
    label.setText("不能裝入指定的Applet");
    }
    // 刷新屏幕
    validate();
    }
    public void appletResize(int width, int height) {
    // 把appletResize調用從stub程序傳遞到Applet
    resize(width, height);
    }
    }

    ?

    編譯后的代碼小于2K,下載速度很快。代碼中有幾個地方值得注意。首先,PreLoader實現了AppletStub接口。一般地,Applet從調用者判斷自己的codebase。在本例中,我們必須調用setStub()告訴Applet到哪里提取這個信息。另一個值得注意的地方是, AppletStub接口包含許多和Applet類一樣的方法,但appletResize()方法除外。這里我們把對appletResize()方法的調用傳遞給了resize()方法。

    3.3 在畫出圖形之前預先裝入它

    ImageObserver接口可用來接收圖形裝入的提示信息。ImageObserver接口只有一個方法imageUpdate(),能夠用一次repaint()操作在屏幕上畫出圖形。下面提供了一個例子。


    public boolean imageUpdate(Image img, int flags, int x, int y, int w, int h) {
    if ((flags & ALLBITS) !=0 {
    repaint();
    }
    else if (flags & (ERROR |ABORT )) != 0) {
    error = true;
    // 文件沒有找到,考慮顯示一個占位符
    repaint();
    }
    return (flags & (ALLBITS | ERROR| ABORT)) == 0;
    }

    當圖形信息可用時,imageUpdate()方法被調用。如果需要進一步更新,該方法返回true;如果所需信息已經得到,該方法返回false。

    3.4 覆蓋update方法

    update()方法的默認動作是清除屏幕,然后調用paint()方法。如果使用默認的update()方法,頻繁使用圖形的應用可能出現顯示閃爍現象。要避免在paint()調用之前的屏幕清除操作,只需按照如下方式覆蓋update()方法:

    public void update(Graphics g) {
    paint(g);
    }

    更理想的方案是:覆蓋update(),只重畫屏幕上發生變化的區域,如下所示:

    public void update(Graphics g) {
    g.clipRect(x, y, w, h);
    paint(g);
    }


    3.5 延遲重畫操作

    對于圖形用戶界面的應用來說,性能低下的主要原因往往可以歸結為重畫屏幕的效率低下。當用戶改變窗口大小或者滾動一個窗口時,這一點通常可以很明顯地觀察到。改變窗口大小或者滾動屏幕之類的操作導致重畫屏幕事件大量地、快速地生成,甚至超過了相關代碼的執行速度。對付這個問題最好的辦法是忽略所有“遲到” 的事件。

    建議在這里引入一個數毫秒的時差,即如果我們立即接收到了另一個重畫事件,可以停止處理當前事件轉而處理最后一個收到的重畫事件;否則,我們繼續進行當前的重畫過程。

    如果事件要啟動一項耗時的工作,分離出一個工作線程是一種較好的處理方式;否則,一些部件可能被“凍結”,因為每次只能處理一個事件。下面提供了一個事件處理的簡單例子,但經過擴展后它可以用來控制工作線程。


    public static void runOnce(String id, final long milliseconds) {
    synchronized(e_queue) { // e_queue: 所有事件的集合
    if (!e_queue.containsKey(id)) {
    e_queue.put(token, new LastOne());
    }
    }
    final LastOne lastOne = (LastOne) e_queue.get(token);
    final long time = System.currentTimeMillis(); // 獲得當前時間
    lastOne.time = time;
    (new Thread() {public void run() {
    if (milliseconds > 0) {
    try {Thread.sleep(milliseconds);} // 暫停線程
    catch (Exception ex) {}
    }
    synchronized(lastOne.running) { // 等待上一事件結束
    if (lastOne.time != time) // 只處理最后一個事件
    return;
    }
    }}).start();
    }
    private static Hashtable e_queue = new Hashtable();
    private static class LastOne {
    public long time=0;
    public Object running = new Object();
    }


    3.6 使用雙緩沖區

    在屏幕之外的緩沖區繪圖,完成后立即把整個圖形顯示出來。由于有兩個緩沖區,所以程序可以來回切換。這樣,我們可以用一個低優先級的線程負責畫圖,使得程序能夠利用空閑的CPU時間執行其他任務。下面的偽代碼片斷示范了這種技術。


    Graphics myGraphics;
    Image myOffscreenImage = createImage(size().width, size().height);
    Graphics offscreenGraphics = myOffscreenImage.getGraphics();
    offscreenGraphics.drawImage(img, 50, 50, this);
    myGraphics.drawImage(myOffscreenImage, 0, 0, this);


    3.7 使用BufferedImage

    Java JDK 1.2使用了一個軟顯示設備,使得文本在不同的平臺上看起來相似。為實現這個功能,Java必須直接處理構成文字的像素。由于這種技術要在內存中大量地進行位復制操作,早期的JDK在使用這種技術時性能不佳。為解決這個問題而提出的Java標準實現了一種新的圖形類型,即BufferedImage。

    BufferedImage子類描述的圖形帶有一個可訪問的圖形數據緩沖區。一個BufferedImage包含一個ColorModel和一組光柵圖形數據。這個類一般使用RGB(紅、綠、藍)顏色模型,但也可以處理灰度級圖形。它的構造函數很簡單,如下所示:
    public BufferedImage (int width, int height, int imageType)

    ImageType允許我們指定要緩沖的是什么類型的圖形,比如5-位RGB、8-位RGB、灰度級等。

    3.8 使用VolatileImage

    許多硬件平臺和它們的操作系統都提供基本的硬件加速支持。例如,硬件加速一般提供矩形填充功能,和利用CPU完成同一任務相比,硬件加速的效率更高。由于硬件加速分離了一部分工作,允許多個工作流并發進行,從而緩解了對CPU和系統總線的壓力,使得應用能夠運行得更快。利用VolatileImage可以創建硬件加速的圖形以及管理圖形的內容。由于它直接利用低層平臺的能力,性能的改善程度主要取決于系統使用的圖形適配器。VolatileImage的內容隨時可能丟失,也即它是“不穩定的(volatile)”。因此,在使用圖形之前,最好檢查一下它的內容是否丟失。VolatileImage有兩個能夠檢查內容是否丟失的方法:

    public abstract int validate(GraphicsConfiguration gc);
    public abstract Boolean contentsLost();

    每次從VolatileImage對象復制內容或者寫入VolatileImage時,應該調用validate()方法。contentsLost()方法告訴我們,自從最后一次validate()調用之后,圖形的內容是否丟失。

    雖然VolatileImage是一個抽象類,但不要從它這里派生子類。VolatileImage應該通過 Component.createVolatileImage()或者 GraphicsConfiguration.createCompatibleVolatileImage()方法創建。

    3.9 使用Window Blitting

    進行滾動操作時,所有可見的內容一般都要重畫,從而導致大量不必要的重畫工作。許多操作系統的圖形子系統,包括WIN32 GDI、MacOS和X/Windows,都支持Window Blitting技術。Window Blitting技術直接在屏幕緩沖區中把圖形移到新的位置,只重畫新出現的區域。要在Swing應用中使用Window Blitting技術,設置方法如下:

    setScrollMode(int mode);

    在大多數應用中,使用這種技術能夠提高滾動速度。只有在一種情形下,Window Blitting會導致性能降低,即應用在后臺進行滾動操作。如果是用戶在滾動一個應用,那么它總是在前臺,無需擔心任何負面影響。

    Java編程思想讀書筆記(對象)

    對象的存儲:

    對象的存儲區域有:寄存器(Registers)、棧(Stack)、堆(Heap)、靜態存儲空間(Static Storage)、常量存儲空間(Constant storage)、Non-RAM存儲空間。

    • 寄存器:寄存器位于處理器內部,由于寄存器個數有限,編譯器根據本身需求適當地分配寄存器使用。
    • 棧:此里用來存儲對像的引用和基本型別的變量。基本型別包括:boolean,char,byte,short,int,long,float,doule,void。
    • 堆:此里用來存儲所有的Java對象。棧里的所有關于對像的引用均指定堆里的具體對象。
    • 靜態存儲空間:用來存儲對象內的特定靜態成員,此靜態成員是用static變量聲明的。但Java對象絕無可能置于靜態存儲空間中。
    • 常量存儲空間:用來存儲常量,常量也可存于ROM只讀內存中。
    • Non-RAM存儲空間:用來存儲串流化對象(streamed objects)和持久性對象(persistent objects)。

    基本數據類型:

    基本型別包括:boolean,char,byte,short,int,long,float,doule,void。(string 屬于對象,不屬于基本類別。)而其對應的外覆型分別是:Boolean,Character,Byte,Short,Integer,Long,Folat,Double,Void。
    別外,Java還提供了兩個高精度計算的Classes:BigInteger(可以精確表示任意長度整數數值,不會在運算過程中喪失任何信息)和BigDecimal(提供任意精度的定點數)。雖然它們也可以視為外覆類,但兩者都沒有對應的基本型別。
    缺省值:當Class的某個成員屬于基本型別時,即使沒有為它提供初值,Java仍保證它有一個缺省值,缺省值如下:
    boolean: false
    char :'/u0000/(null)
    byte : (byte)0
    short : (short)0
    int : 0
    long : 0L
    float : 0.0f
    double 0.0d
    但是:只有當變量身份是“Class內的成員”時,Java才保證為該變量提供初值。但當變量屬于局域變量(如位于某個函數內時)時,Java并不提供初始值。如:int x 。x可能是任意值(和C/C++)中的一樣,不會被自動設為0.
    所有數組的初始值為null。

    垃圾回收:
    當一個對象不再被引用后,其Reference會在棧內消失,當垃圾收集器在堆內檢測對象,發現有的對象不再有Reference引用指向它時,就會把它銷毀。


    游戲框架設計Ⅰ—— 游戲中的事件機制

    游戲框架設計Ⅰ—— 游戲中的事件機制
    ? 
     
      事件機制在很多高級程序設計語言中都有支持。譬如VB、C#(delegate)、C++Builder(并不屬于C++的范疇。C++Builder中的事件處理器必須用關鍵字closure<閉包>修飾)等等,甚至在HTML中也可以見到它的身影。事件機制的引入使軟件系統變得更加易于理解——它使一種語言(平臺)更加接近于這個世界的真相。事情的發展變得像現實世界中那樣順理成章。某一事件的產生引發了一系列其他事件的產生,這些事件要么是結果要么又會引發一系列事件的產生......如此這般,信息才得以在事件的新陳代謝中延續,世界才得以向前發展。在某些游戲設計過程中的一項重要任務就是模擬現實世界的某些特征,以期實現機器與用戶的更加親密的溝通。事件機制就是很好的一例。我們需要事件來使我們的系統更加人性化。

      我想,在我繼續進行下面對討論之前,先簡單介紹一下"事件"這個東東。


    ?


    ?


    1. 游戲中的事件機制

      聯系是普遍存在的。事事有聯系、時時有聯系,整個世界是一個相互聯系的統一整體。一個人的行為、物的狀態的改變或事的進展過程的某一階段可以引發一個事件。一個事件的發生或許會引發另外的事件——通過人的感知、大腦的反映,然后作出決策,付諸行動——也或許,就這么蒸發掉,無人知曉。但無論如何,在這一過程中,我們總能抽象出一些實質性的東西來。就像下面的圖示:
     

    ?

    ?

    在游戲中:

      事件源——表示任何可以引發事件的對象。譬如,一個"人"、"坦克"、"建筑物"、"地面"。

      事件——表示任何可以處理的事件。譬如,"感冒"、"射擊"、"倒塌"、"有對象經過"。

      響應者——表示任何對某事件感興趣的對象。

      響應器——表示對某事件感興趣的對象對某一確定事件作出的反應。

    特別的,對于過程:

      通知——發生在事件與響應者之間。我們把它分為兩種方式:有限聽眾式、廣播式。對事件感興趣的對象(響應者)只有確定的有限個(只有一個的情況下,可以叫做點對點式)的情況就是有限聽眾式。而對于廣播式,事件并不知道會有哪些(個)對象對自己感興趣。它向所有可以接收事件通知的對象廣播事件。

      觸發——響應者發現自己對特定事件需要做出相應的行動時就會觸發事件處理器,并同時傳遞需要的事件信息給它。對于響應者,它也可以選擇沉默——自己了解事件但并不作出行動。因此這個過程的決定權在響應者手上。


    2. 萬事之鼻祖 Event

      我們需要一個類來表示所有事件的普遍性質。

    public class Event {

    ???? // 屬性

    ???? public string Name { get;set; }// 獲取或設置事件的名稱

    ???? public string Message { get;set; }// 獲取或設置事件的簡單描述

    ???? EventTypes EventType { get;set; }// 獲取或設置事件類型(枚舉EventTypes)

    ???? ListenerCollection Listeners { get; } // 獲取響應者的集合

    ???? public bool PoolEvent { get;set; }// 獲取或設置事件的簡單描述

    ?

    ???? // 方法

    ???? void RaiseEvent(); // 通知響應者事件的發生

    ???? void AbandonListener( int index ); // 拋棄一個事件響應者,并把它從 Listeners 中移除。

    ???? void AbandonListener(); // 拋棄所有的事件響應者

    }
    ?


    3. 枚舉類型 EventTypes

      這個枚舉類型指示事件通知過程的類型:有限聽眾式、廣播式。

    public enum EventTypes {

    ???? LimitedListener ,

    ???? Broadcast

    }
    ?


    4. 響應者接口 IListener

      該接口只有唯一的方法 EventArrived() 。事件發生時會調用這個方法并傳遞相關參數。這個參數必須是 EventArgs 或由它派生而來。

    public interface IListener {

    ???? // 通知一個響應者事件的到達。

    ???? void EventArrived( EventArgs args );

    }
    ?


    5. EventPool

      一個事件池。當且僅當需要事件廣播時我們才需要它。需要注意的是 AddEvent 方法。它把一個事件添加到池中,第二個參數指定是否將該事件已經指定的響應者亦添加到廣播的響應者中。事件添加后,其 Event::EventType 屬性會被設置為 EventTypes.Broadcast。

    public class EventPool {

    ???? // 屬性

    ???? public ArrayList Events { get; }// 獲取池中所有的事件的集合

    ???? public ListnerCollection Listners { get; }// 獲取池中所有的響應者的集合

    ?

    ???? // 方法

    ???? void AddEvent( Event obj ,bool copyListners ); // 添加一個事件并把它作為廣播式事件

    ???? void RemoveEventAt( int index ); // 將一個事件從列表中移除

    ???? void RemoveEvent( Event listener ); // 將一個事件從列表中移除

    ???? void Broadcast( Event event ); // 向列表中的所有響應者廣播指定事件(可以是非池中的事件)

    ???? void BroadcastItemAt( int index ); // 向列表中的所有響應者廣播池中的指定事件

    }
    ?


    6. EventArgs

    public class EventArgs {

    ???? public Event Event { get; } // 獲取傳遞這個參數的事件

    ???? public object Sender { get; } // 獲取事件源

    }
    ?


    7. UML Diagram


    ?


    8. 響應者行為

      響應者實現 IListener 接口后就可以響應事件了。在 EventArrived() 方法中,你可以直接處理事件,抑或是調用其它的事件處理器(響應器)。C#中有很好的解決方案——委托——替代函數指針的最有效的方法。在C++中也可以用虛擬函數表來模擬委托機制。總之,在響應器上的解決方案是很靈活的。在實際開發中,可以根據不同的環境做出不同的選擇。


    9. 擴展機制

      在一個游戲中,除了已經定義好的事件外,其劇情或功能可能會要求玩家自行定義一些事件。這就需要一種可擴展的方案。我們引入了 CustomEvent 類——繼承自 Event,以及 Condition 類。

    public class CustomEvent : Event {

    ???? public CustomEvent( Condition condition ) {

    ????????? _Condition = condition;

    ???? }

    ?

    ???? public Condition TestCondition { get{ return _Condition; } }

    ?

    ???? Condition _Condition = null;

    }

    ?

    public abstract class Condition {

    ???? public Condition() {}

    ???? bool abstract Test();

    }
    ?

      初始化一個 CustomEvent 類時必須同時傳入一個 Condition 類。Condition 類必須被繼承。Test()方法在適當的時候被調用以檢測是否可以引發這個事件。


    10. 后記

      以上談到的只是一個簡單的模型,是否實用還要等待實踐的檢驗。歡迎讀者的批評與建議



    使用Java NIO提高服務端程序的性能

    在前面的章節里,我們討論了Java NIO的基本概念,在這一節里,我們將結合具體的Java Socket編程,討論使用NIO提高服務端程序的性能的問題。

    ????Java NIO增加了新的SocketChannel、ServerSocketChannel等類來提供對構建高性能的服務端程序的支持。 SocketChannel、ServerSocketChannel能夠在非阻塞的模式下工作,它們都是selectable的類。在構建服務器或者中間件時,推薦使用Java NIO。

    ????在傳統的網絡編程中,我們通常使用一個專用線程(Thread)來處理一個Socket連接,通過使用NIO,一個或者很少幾個Socket線程就可以處理成千上萬個活動的Socket連接。

    ????通常情況下,通過ServerSocketChannel.open()獲得一個ServerSocketChannel的實例,通過SocketChannel.open或者serverSocketChannel.accept()獲得一個SocketChannel實例。要使ServerSocketChannel或者SocketChannel在非阻塞的模式下操作,可以調用
    ????serverSocketChannel.configureBlocking (false);
    ????或者
    ????socketChannel.configureBlocking (false);

    ????語句來達到目的。通常情況下,服務端可以使用非阻塞的ServerSocketChannel,這樣,服務端的程序就可以更容易地同時處理多個socket線程。

    ????下面我們來看一個綜合例子,這個例子使用了ServerSocketChannel、SocketChannel開發了一個非阻塞的、能處理多線程的Echo服務端程序,見示例12-14。
    ????【程序源代碼】

    1 // ==================== Program Discription ===================== 2 // 程序名稱:示例12-14 : SocketChannelDemo.java 3 // 程序目的:學習Java NIO#SocketChannel 4 // ============================================================== 5 6 7 import java.nio.ByteBuffer; 8 import java.nio.channels.ServerSocketChannel; 9 import java.nio.channels.SocketChannel; 10 import java.nio.channels.Selector; 11 import java.nio.channels.SelectionKey; 12 import java.nio.channels.SelectableChannel; 13 14 import java.net.Socket; 15 import java.net.ServerSocket; 16 import java.net.InetSocketAddress; 17 import java.util.Iterator; 18 19 public class SocketChannelDemo20 21 { 22 public static int PORT_NUMBER = 23;//監聽端口 23 ServerSocketChannel serverChannel; 24 ServerSocket serverSocket ; 25 Selector selector ; 26 private ByteBuffer buffer = ByteBuffer.allocateDirect (1024); 27 28 public static void main (String [] args) 29 throws Exception 30 { 31 SocketChannelDemo server=new SocketChannelDemo(); 32 server.init(args); 33 server.startWork(); 34 } 35 36 37 public void init (String [] argv)throws Exception 38 { 39 int port = PORT_NUMBER; 40 41 if (argv.length > 0) { 42 port = Integer.parseInt (argv [0]); 43 } 44 45 System.out.println ("Listening on port " + port); 46 47 // 分配一個ServerSocketChannel 48 serverChannel = ServerSocketChannel.open(); 49 // 從ServerSocketChannel里獲得一個對應的Socket 50 serverSocket = serverChannel.socket(); 51 // 生成一個Selector 52 selector = Selector.open(); 53 54 // 把Socket綁定到端口上 55 serverSocket.bind (new InetSocketAddress (port)); 56 //serverChannel為非bolck 57 serverChannel.configureBlocking (false); 58 59 // 通過Selector注冊ServerSocetChannel 60 serverChannel.register (selector, SelectionKey.OP_ACCEPT); 61 62 } 63 64 public void startWork()throws Exception65 66 { 67 while (true) { 68 69 int n = selector.select();//獲得IO準備就緒的channel數量 70 71 if (n == 0) { 72 continue; // 沒有channel準備就緒,繼續執行 73 } 74 75 // 用一個iterator返回Selector的selectedkeys 76 Iterator it = selector.selectedKeys().iterator(); 77 78 // 處理每一個SelectionKey 79 while (it.hasNext()) { 80 SelectionKey key = (SelectionKey) it.next(); 81 82 // 判斷是否有新的連接到達 83 if (key.isAcceptable()) { 84           //返回SelectionKey的ServerSocketChannel 85 ServerSocketChannel server = (ServerSocketChannel) key.channel(); 86 SocketChannel channel = server.accept(); 87 88 registerChannel (selector, channel, 89 SelectionKey.OP_READ); 90 91 doWork (channel); 92 } 93 94 // 判斷是否有數據在此channel里需要讀取 95 if (key.isReadable()) { 96 97 processData (key); 98 99 } 100 101 //刪除 selectedkeys 102 it.remove(); 103 } 104 } 105 } 106 protected void registerChannel (Selector selector, 107 SelectableChannel channel, int ops) 108 throws Exception 109 {110 if (channel == null) { 111 return; 112 } 113 114 115 channel.configureBlocking (false); 116 117 channel.register (selector, ops); 118 } 119 120 //處理接收的數據 121 protected void processData (SelectionKey key) 122 throws Exception 123 { 124 125 126 SocketChannel socketChannel = (SocketChannel) key.channel(); 127 int count; 128 129 buffer.clear(); // 清空buffer 130 131 // 讀取所有的數據 132 while ((count = socketChannel.read (buffer)) > 0) { 133 buffer.flip(); 134 135 // send the data, don′t assume it goes all at once 136 while (buffer.hasRemaining()) 137 { 138 //如果收到回車鍵,則在返回的字符前增加[echo]$字樣 139 if(buffer.get()==(char)13) 140 { 141 buffer.clear(); 142 buffer.put("[echo]___FCKpd___0quot;.getBytes()); 143 buffer.flip(); 144 145 } 146 socketChannel.write (buffer);//在Socket里寫數據 147 } 148 149 buffer.clear(); // 清空buffer 150 } 151 152 if (count < 0) { 153 // count<0,說明已經讀取完畢 154 socketChannel.close();155 } 156 } 157 158 159 private void doWork (SocketChannel channel)throws Exception 160 { 161 buffer.clear(); 162 buffer.put (" Hello,I am working,please input some thing,and i will echo to you! [echo] ___FCKpd___0quot;.getBytes()); 163 buffer.flip(); 164 channel.write (buffer); 165 } 166 167 }


    ????使用:運行此程序,然后在控制臺輸入命令telnet localhost 23。

    ????【程序輸出結果】如圖12-1所示。






    圖12-1 輸出結果

    ????【程序注解】
    ????關于程序的解釋已經包含在程序里面了,在這里我們總結以下使用ServerSocket Channel開發服務端程序的過程:
    ????(1)分配一個ServerSocketChannel。
    ????(2)從ServerSocketChannel里獲得一個對應的ServerSocket。
    ????(3)生成一個Selector實例。
    ????(4)把ServerSocket綁定到端口上。
    ????(5)設置ServerSocketChannel為非block模式(可選)。
    ????(6)在Selector里注冊ServerSocetChannel。
    ????(7)用一個無限循環語句始終查看Selector里是否有IO準備就緒的channel。如果有,就執行對應的處理,如果沒有,繼續循環。

    ???? 小 結

    ????在本章我們主要介紹了Java中的網絡編程。Java一開始就是一種網絡編程語言,到后來才應用到各個方面,所以在Java中進行網絡編程遠比在C/C++中方便。

    ????我們介紹了幾個在網絡編程中很重要的類,如InetAddress、URL、URLConnection、Socket、 ServerSocket、DatagramSocket、DatagramPacket、MulticastSocket等。這些類包含了進行基本網絡編程的所有內容。要熟練地應用這些類,關鍵還是要多多練習。

    ????基于套接字的編程基本上是客戶/服務器模式,我們具體介紹了編寫這種模式的步驟。在實例方面,我們給出了一個基于TCP的套接字客戶/服務器程序,與此相對應,還給出了基于UDP的客戶/服務器程序。兩者的模式是很相似的,其實這也就是編寫客戶/服務器程序的一般模式。 (T111)


    總結

    以上是生活随笔為你收集整理的提高编程思想的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    久久免费看a级毛毛片 | 欧美另类视频 | 国产精品 欧美 日韩 | 亚洲人人爱| 探花视频在线观看免费版 | 在线观看你懂的网址 | 91精品视频免费看 | 久久69精品久久久久久久电影好 | 一级黄色av | 婷香五月| 欧美精品久久99 | 国产经典av | 91最新网址在线观看 | 99精品免费在线 | 国产一区二区三区高清播放 | 久久精品久久99精品久久 | 天天舔夜夜操 | 91精品久久久久久久久久久久久 | 久久久精品小视频 | 97电影在线| 久久久国产高清 | 国产精成人品免费观看 | 久久影院一区 | 天天天天天天天操 | 亚洲伦理精品 | 一级片免费视频 | 日本在线观看一区 | 国产特级毛片aaaaaaa高清 | 色丁香婷婷 | av黄色av| 国产欧美最新羞羞视频在线观看 | 精品一区二区久久久久久久网站 | 亚洲,国产成人av | 久久99免费视频 | 亚洲在线观看av | 亚洲精品视频在线看 | 五月婷婷六月丁香 | 日韩欧美在线视频一区二区三区 | 精品一区二区免费视频 | 国产精品九九九九九 | 久草在线观看资源 | japanesefreesex中国少妇 | 九九影视理伦片 | 蜜臀av网址 | 久久综合视频网 | 成年人免费在线观看网站 | 久久精品观看 | 中文字幕免费 | 国产黄 | 欧美日产在线观看 | 在线视频欧美日韩 | 久热免费在线观看 | 四虎影视精品 | 人人爽爽人人 | 在线观看视频99 | 国产精品视频全国免费观看 | 亚洲女欲精品久久久久久久18 | 久久精品亚洲综合专区 | 国产第一页在线观看 | 久久大片 | 久久精品99国产国产精 | 亚洲精品国产精品国自产 | 成人午夜久久 | 97成人免费视频 | 久久久久久久久免费视频 | 国产97在线观看 | 亚洲一区精品人人爽人人躁 | 色久网 | 91中文在线 | 国产精品美女免费看 | 国产不卡一 | 久久国产欧美日韩精品 | 日本在线观看视频一区 | 国产九色视频在线观看 | 久久久久久久免费 | 亚洲日本va中文字幕 | 久久久精品一区二区三区 | 久久综合婷婷综合 | 不卡视频一区二区三区 | 高清av中文在线字幕观看1 | 久草精品视频在线播放 | 久久99精品波多结衣一区 | 美女久久一区 | 国产精品成人久久 | 久久久国产精品电影 | 视频三区在线 | 激情视频一区二区三区 | 国产精品久久久视频 | 美腿丝袜一区二区三区 | 亚洲va欧美 | 免费日韩精品 | 操久久免费视频 | 亚洲女同videos | 午夜精品久久久久久久99婷婷 | 日本动漫做毛片一区二区 | 亚洲视频2 | 国产精品完整版 | 天天摸天天舔 | 久久亚洲免费 | 亚洲综合婷婷 | 操操操天天操 | 免费av高清| 亚洲一区免费在线 | 亚洲国产中文字幕在线观看 | 最新国产精品久久精品 | 成人三级网站在线观看 | 粉嫩av一区二区三区免费 | 午夜精品久久久久久中宇69 | 在线小视频| 成人网在线免费视频 | 久产久精国产品 | 日本精品一区二区三区在线播放视频 | 麻豆视频免费在线观看 | 亚洲专区在线播放 | 国产一级二级在线观看 | 午夜精品一区二区三区在线观看 | 国产欧美精品一区aⅴ影院 99视频国产精品免费观看 | 久久色视频 | 国产 日韩 中文字幕 | 亚洲va欧洲va国产va不卡 | 国产精品成人免费 | 激情小说 五月 | 国产麻豆电影在线观看 | 久久永久免费 | 中文字幕在线播放视频 | 久久99精品久久久久久清纯直播 | 91av官网| 免费视频91蜜桃 | 亚洲永久精品在线观看 | 99理论片| 午夜精品久久久久久99热明星 | 国产视频二区三区 | 91麻豆精品一区二区三区 | 国产二区av | 手机av电影在线 | 久久成人资源 | 欧美一级性生活片 | 亚a在线| 成人黄大片视频在线观看 | 亚洲成人中文在线 | 久久精美视频 | 久久a级片 | 久草在线免费色站 | 久久观看最新视频 | 色综合天天色综合 | 黄色软件视频大全免费下载 | 黄色av在 | avove黑丝 | 一区二区视频在线免费观看 | 国产成人一区二区三区影院在线 | 日本天天色 | 午夜av色| 国产精品乱码一区二三区 | 中文亚洲欧美日韩 | 五月婷婷开心中文字幕 | 亚洲国产伊人 | 国内精品国产三级国产aⅴ久 | 免费精品在线视频 | 久久人人爽人人爽 | 在线观看免费国产小视频 | 国产91学生粉嫩喷水 | 五月天综合网站 | 久久久久久久久亚洲精品 | 国产精品精品久久久久久 | 在线观看免费一区 | 久久福利在线 | 一级欧美一级日韩 | 国产另类av | 国产一区观看 | 人人干人人添 | 国产一区二区三区 在线 | 99精品免费久久久久久日本 | 国产精品99精品久久免费 | 免费在线日韩 | 日本老少交 | 成年人免费av | 国产在线精品视频 | 欧美日韩不卡在线视频 | 国产偷v国产偷∨精品视频 在线草 | 99久久99久久精品免费 | 天天夜操 | 91精品国产91久久久久 | 免费在线观看成人av | 91视频午夜 | 国产精品美 | 久久视频国产 | 欧美成人精品三级在线观看播放 | 麻豆视频免费在线 | 亚洲久草视频 | 久久综合给合久久狠狠色 | 337p西西人体大胆瓣开下部 | 精品99免费| 免费看黄在线 | 中文字幕第一 | 国产免费黄色 | 涩涩网站在线看 | 九九综合久久 | 综合国产视频 | www免费看片com | 免费观看mv大片高清 | 91黄色免费看 | 免费看在线看www777 | 免费三级影片 | 欧美另类z0zx| 日日操夜夜操狠狠操 | 成年人黄色在线观看 | 欧美夫妻性生活电影 | 99在线视频精品 | 国产精品久久久777 成人手机在线视频 | 麻豆久久 | 久久久久久久久毛片 | 在线精品亚洲一区二区 | 国产精品美女久久久久久久 | 国产专区一| 四虎国产精品成人免费影视 | 久久网址| 麻豆视频www | 亚洲成人频道 | 亚洲精品免费在线播放 | 精品中文字幕在线观看 | 久久久综合电影 | 91porny九色91啦中文 | 久草在在线视频 | 手机成人免费视频 | 在线中文视频 | 日日狠狠 | 欧美日韩在线免费视频 | 精品久久久久久久久久国产 | 蜜臀aⅴ精品一区二区三区 久久视屏网 | 欧美一级激情 | 在线黄色国产电影 | 亚洲精品久久久久久中文传媒 | 国产精品成人在线观看 | 免费污片 | 欧美日韩视频在线观看免费 | 亚洲精品视频一二三 | 久久精选视频 | 欧美精品一区二区在线播放 | 亚洲国产成人在线观看 | 亚洲毛片久久 | 网站免费黄色 | 黄色片免费看 | a级片网站| 天天爱天天射天天干天天 | 中文成人字幕 | 中文字幕精品一区二区三区电影 | 亚洲精品合集 | 亚洲精品国产第一综合99久久 | 欧美日韩精品区 | 天天插综合 | 91中文字幕在线播放 | 国产精品久久久久久久久久免费 | 高清av影院 | a级片网站 | 日本中文不卡 | 国产精品免费一区二区三区在线观看 | 婷婷丁香花五月天 | 久久久久久久免费观看 | 九九在线免费视频 | 97在线视频免费观看 | 精品国产成人av在线免 | 亚洲专区视频在线观看 | 国产成人精品区 | 国产亚洲精品美女 | 西西人体www444 | 狠狠五月天 | 国产一区国产精品 | 在线视频免费观看 | 天天色天天综合网 | 免费高清在线观看成人 | 草免费视频 | 特级西西444www高清大视频 | 玖玖视频免费在线 | 夜色资源站国产www在线视频 | www.亚洲激情.com| 欧美精品少妇xxxxx喷水 | 日韩av视屏在线观看 | 亚洲精品啊啊啊 | 国产一性一爱一乱一交 | 国产精品欧美日韩 | 免费在线观看一区 | 波多野结衣动态图 | 欧美va电影| 国产资源免费在线观看 | 亚洲国产wwwccc36天堂 | 免费在线观看一级片 | 五月婷婷一级片 | www.色婷婷 | 天天夜夜亚洲 | 欧美做受69 | 91精品一 | 亚洲最大成人网4388xx | 天天操天天射天天爱 | av资源免费在线观看 | 成年人视频在线免费观看 | 久久看片网站 | 久久亚洲免费视频 | 国产精品免费在线播放 | 天堂在线视频免费观看 | 国产美女久久 | 四虎8848免费高清在线观看 | 五月天综合激情网 | 91av视频免费在线观看 | www.xxx.性狂虐 | 国产玖玖在线 | 久久精品视频在线 | 久久天天综合网 | 日日操网站 | 青青久视频 | 91精品视频在线观看免费 | 97视频在线免费播放 | 久久精品精品 | 日韩黄色软件 | 精品一区二区三区电影 | 国产精品区免费视频 | 国产99区 | 91视频在线观看免费 | 国产精品一区免费在线观看 | 亚洲男男gaygay无套 | 成人免费色 | 欧美精品久久久久久久久久久 | 欧洲色综合 | 91麻豆精品国产自产在线游戏 | 国产 亚洲 欧美 在线 | 欧美日韩一级久久久久久免费看 | 亚洲日本国产精品 | 亚洲国产精品视频在线观看 | 精品中文字幕在线播放 | www.玖玖玖| 成人黄色电影在线观看 | 99久久久免费视频 | av一区在线播放 | 麻豆91小视频 | 97网在线观看 | 一区二区三区视频 | 久久久影院一区二区三区 | 亚洲人视频在线 | 探花视频在线观看+在线播放 | 天天摸天天干天天操天天射 | 美女视频黄免费 | 国产麻豆精品在线观看 | 国产剧情一区二区在线观看 | 久久成人在线 | 色大片免费看 | 国产在线无 | 337p日本大胆噜噜噜噜 | 精品国产视频在线观看 | 中文在线a√在线 | 日本精品在线视频 | 18+视频网站链接 | 亚洲国产精品视频在线观看 | 国产精品视频免费 | 96超碰在线 | 麻豆 91 在线 | 9色在线视频 | 亚州国产精品久久久 | 午夜精品一区二区三区在线视频 | a√天堂中文在线 | 日韩高清www | 日本韩国精品一区二区在线观看 | 狠狠婷婷| 亚洲免费在线视频 | 久久www免费视频 | www.黄色| 亚洲精品综合一区二区 | 夜夜干天天操 | 国内精品小视频 | 久久精品黄 | 国产99一区 | 97超碰人人澡人人 | 欧美三级在线播放 | 丁五月婷婷 | 国产97超碰| 国产不卡一 | 国产精品麻豆99久久久久久 | 香蕉国产91 | 不卡在线一区 | 久久五月婷婷丁香社区 | 成人一级电影在线观看 | 999成人 | 四虎在线观看视频 | 99精品在线看| 夜夜干天天操 | 香蕉视频一级 | 中文字幕在线视频一区二区 | 免费看久久 | 中文视频一区二区 | 天天操天天干天天综合网 | 99热最新地址 | 在线成人观看 | 国产成人免费在线 | www.久久婷婷 | 在线免费性生活片 | 麻豆免费看片 | 日韩精品视频网站 | 国产97在线观看 | 久久精品精品电影网 | 亚洲 欧美 变态 国产 另类 | 免费观看福利视频 | 日日夜夜免费精品视频 | 亚洲成人国产 | 中文视频在线 | 久久综合激情 | 精品视频免费看 | 国产成人黄色网址 | 蜜臀av性久久久久蜜臀aⅴ四虎 | 欧美日本日韩aⅴ在线视频 插插插色综合 | 久久亚洲在线 | 521色香蕉网站在线观看 | 国产午夜精品av一区二区 | 久久精品在线 | 丁香婷婷综合激情 | 丁香综合av | 中文字幕在线视频国产 | 亚洲理论片 | 久久视频网 | 西西444www大胆高清视频 | 色999五月色| zzijzzij日本成熟少妇 | 激情欧美在线观看 | 欧美午夜精品久久久久 | 国产精品一区二区免费在线观看 | 精品国产人成亚洲区 | 国产精品手机在线观看 | 欧美一区二区免费在线观看 | 国产精品久久久久久久午夜片 | 中文字幕在线看 | 最近中文字幕国语免费av | 日日夜夜网 | 国产精品你懂的在线观看 | 91视频观看免费 | 国产黄色片久久久 | 少妇高潮冒白浆 | 国产不卡毛片 | 欧美吞精| 久久国产精品色婷婷 | 91精品国产亚洲 | 成人免费视频免费观看 | 久艹在线观看视频 | 青春草国产视频 | 91免费网址| 欧洲激情综合 | 欧美少妇xx | 国产精品毛片久久久久久久久久99999999 | av三级av | 日本动漫做毛片一区二区 | 成人黄色电影免费观看 | 欧美成人h版 | 欧美亚洲久久 | www.亚洲精品 | 天天综合色天天综合 | 91黄色在线视频 | 波多野结衣动态图 | 天堂va在线观看 | 国产精品 中文字幕 亚洲 欧美 | 国产福利av| 久草综合在线观看 | 五月天综合色 | 色婷婷久久久综合中文字幕 | 99久久综合狠狠综合久久 | 国产一级视频在线 | 久久久久9999亚洲精品 | 国产美女在线精品免费观看 | 在线免费黄色av | 在线看的av网站 | 国产精品欧美久久久久无广告 | 色综合天天狠天天透天天伊人 | 欧美日韩中文在线视频 | 麻豆系列在线观看 | 国产麻豆电影在线观看 | 一级做a爱片性色毛片www | 久久久久久久久久影院 | 99久久99久国产黄毛片 | 美女免费视频黄 | 亚洲自拍偷拍色图 | 欧美乱码精品一区二区 | www.色综合.com | 国产99久久精品一区二区300 | 在线视频欧美日韩 | 91免费观看网站 | av黄色在线观看 | 国产精品毛片一区二区 | 97av视频在线观看 | 日韩在线视频网站 | 香蕉视频导航 | 91免费观看网站 | 色久网 | 日韩女同av | 国产一区二区精品91 | av观看久久久| 91精品国产综合久久福利不卡 | 九九热在线免费观看 | 九九精品视频在线 | 精品欧美一区二区精品久久 | 日韩av在线免费播放 | 亚洲综合成人在线 | 亚洲人成人99网站 | 亚洲视频久久久久 | 久久综合之合合综合久久 | 国产成人精品一区二区三区在线观看 | 国产精品露脸在线 | 91在线免费视频 | 久草青青在线观看 | 国产精品自产拍在线观看 | 国产一区在线免费 | 国产精品www| 91九色国产| 碰碰影院 | 五月天中文字幕 | 成人午夜电影在线 | 久久久久综合视频 | 天天操网 | 人人爱在线视频 | 国产精品一区电影 | 国产对白av | 中文资源在线播放 | 中文字幕在线播放视频 | 免费三级黄色片 | 国产手机视频 | 日日夜夜精品免费 | 青青河边草免费观看 | 国产精品久久久777 成人手机在线视频 | 人人看看人人 | 99精品在线视频播放 | 国产一区二区在线免费播放 | 亚洲精品国产电影 | 免费a级毛片在线看 | 国产精品福利无圣光在线一区 | 国产精品久久久久久影院 | 国语麻豆 | 九九九九热精品免费视频点播观看 | 中文字幕91视频 | 中午字幕在线 | 成 人 a v天堂| 99视频在线精品国自产拍免费观看 | 日韩免费视频观看 | 欧美视频xxx | 美女黄频 | 精品视频在线视频 | 成人毛片a | 中文字幕乱码日本亚洲一区二区 | 亚洲伊人网在线观看 | 天天干天天草天天爽 | 欧美精品久久久久性色 | 亚洲激情在线视频 | 日韩女同一区二区三区在线观看 | 国产 日韩 在线 亚洲 字幕 中文 | 黄色a在线| 毛片基地黄久久久久久天堂 | 精品亚洲国产视频 | 808电影| 国产美女视频免费 | 黄色av三级在线 | 精品视频在线播放 | 在线观看午夜av | 日韩精品观看 | 国产区免费 | 日韩女同av | 欧美视频www | 免费大片av| 精品久久久久免费极品大片 | 波多野结衣网址 | 亚洲成av人片在线观看www | 婷婷激情五月 | 国产精品久久久久久久久久不蜜月 | 国产a级免费 | 91在线看片 | 午夜神马福利 | 免费黄色一区 | 2019精品手机国产品在线 | 欧美a级在线播放 | 黄色三级免费 | 中文字幕第一 | 国产精品久久久久久999 | 99久久日韩精品视频免费在线观看 | 婷婷伊人网 | 久久久久女人精品毛片九一 | 久久久免费 | 国产精品18毛片一区二区 | 久久国产精品小视频 | av久久久| 精壮的侍卫呻吟h | 日日操夜夜操狠狠操 | 国产一区二区在线看 | 综合网中文字幕 | 日韩久久精品一区 | 免费久久网 | 97碰视频| 黄色毛片一级片 | 精品国产视频在线 | 蜜桃视频在线视频 | 69av国产| 精品国产1区二区 | 伊人午夜视频 | 日本精品视频免费 | 午夜视频99 | 天堂av影院| 日韩专区中文字幕 | 91精品久久久久久粉嫩 | 色999五月色| 精品色999 | 久久免费视频网站 | 91大神dom调教在线观看 | 久久免费视频播放 | 日韩免费高清 | 久久黄视频| 精品一区 在线 | 日日添夜夜添 | 天天射综合 | 人人爽人人片 | 男女精品久久 | 四虎影视4hu4虎成人 | 日韩av一区二区三区四区 | 精品在线一区二区三区 | 久青草视频在线观看 | 精品国产大片 | 91成人精品| 国产亚洲精品久久久网站好莱 | 日韩在线视频网站 | 九九热精品视频在线观看 | 亚洲国产视频在线 | 国产精品丝袜久久久久久久不卡 | 日韩极品在线 | 国产精品一区二区三区观看 | 天天艹日日干 | 国产一区欧美在线 | 最近中文字幕免费大全 | 国产精品毛片久久 | 日韩高清在线一区 | 久久久毛片 | 999视频网站 | 黄色成人毛片 | 五月婷婷丁香在线观看 | 国产在线a | 亚洲日本精品视频 | 一区二区免费不卡在线 | 五月天国产 | 成人毛片在线观看视频 | 最近中文字幕视频网 | 91av超碰| 成人精品电影 | 中文字幕日本在线 | 亚洲国产精品资源 | 最新日韩精品 | 91久草视频| 免费看搞黄视频网站 | 亚洲综合色站 | 国内亚洲精品 | 久久国产精品小视频 | 国内精品久久久久久久久 | 婷婷av在线 | 国产精品18久久久久vr手机版特色 | 国产精品久久久久一区二区三区 | 国内亚洲精品 | 亚洲成av | 久久全国免费视频 | 久要激情网| 中文乱幕日产无线码1区 | 免费成人av在线看 | 欧美日韩性 | 又黄又爽又湿又无遮挡的在线视频 | 九九三级毛片 | 亚洲欧美日韩一级 | 一区二区三区四区五区在线视频 | 在线免费国产 | 欧美日本不卡视频 | 国产成人一区二区精品非洲 | 国产小视频免费观看 | 亚洲区另类春色综合小说 | 五月天婷婷在线播放 | 五月婷婷黄色 | www黄色软件| 伊人热| 日韩在线首页 | 亚洲精品影视在线观看 | 97久久久免费福利网址 | 最近最新中文字幕 | 欧美俄罗斯性视频 | 99精品国产高清在线观看 | 美女网站色在线观看 | 成人黄色中文字幕 | 激情网综合 | 中文字幕av免费在线观看 | 欧美激情视频三区 | 91精品久久久久久粉嫩 | 日韩在线三级 | 成人a级黄色片 | 久久色中文字幕 | 国产免费久久av | 欧美日韩国产精品一区二区 | 国产中文a | 福利一区在线 | 99精品在线观看视频 | www.成人sex| 97精品国自产拍在线观看 | 97超碰资源| 美州a亚洲一视本频v色道 | 欧美成人一区二区 | 成年人黄色大片在线 | 国产精品 日韩 欧美 | 狠狠色丁香久久综合网 | 在线天堂中文在线资源网 | 欧美成人播放 | 91网页版在线观看 | 国产又粗又猛又爽 | 精品少妇一区二区三区在线 | 久插视频 | 国产一二区视频 | 日韩在线影视 | 一区二区三区国产精品 | 午夜国产一区二区 | 免费a视频在线 | 亚洲人人av | 成人资源在线观看 | 在线看av的网址 | 国产精品二区在线观看 | 欧美婷婷综合 | 久久五月天色综合 | 亚洲国产高清视频 | 国产精品情侣视频 | 激情丁香5月 | 久久国产精品久久国产精品 | 亚色视频在线观看 | 久久资源总站 | 国产永久网站 | 黄色av网站在线观看免费 | av免费线看 | 免费在线一区二区 | 国产99视频在线观看 | 久久视| 欧美国产日韩一区二区三区 | av大全在线免费观看 | 一区二区三区精品在线视频 | 五月天国产精品 | 国产午夜影院 | 91成人网在线 | 久久国语| 国产成人精品av在线观 | 最近中文字幕国语免费高清6 | 日韩在线视频网站 | 91精品一区在线观看 | 天天伊人狠狠 | 久久亚洲在线 | 成年人天堂com | 夜夜视频| 69av视频在线 | 日韩在线视频一区二区三区 | 久久精品高清视频 | 正在播放一区 | 天天干天天操天天射 | 精油按摩av| 国产黄色看片 | 不卡国产视频 | 欧洲精品在线视频 | 国产成人精品一区二区三区福利 | 五月天com| 一级黄色片在线观看 | 夜夜躁日日躁狠狠躁 | 精品视频久久久 | 黄色日视频 | 国产高清视频在线 | 天天色视频 | 日本精品视频在线观看 | 99爱在线观看 | 在线看片视频 | 精品国产99| 成人av在线一区二区 | 亚洲精品动漫久久久久 | 欧美日韩在线观看视频 | 日韩欧美一区视频 | 欧美日韩视频在线观看免费 | 日日夜夜av | 久草在线最新免费 | www.亚洲黄色| 婷婷夜夜| 日本中文一级片 | 丁香综合网| 激情婷婷六月 | 一区二区视频播放 | 国产手机视频 | 色妞色视频一区二区三区四区 | 久久公开免费视频 | 久久新 | 国产区高清在线 | 在线激情小视频 | 国产 欧美 日韩 | 91黄色在线视频 | 久久久国产精品麻豆 | 中文字幕在线第一页 | 国产精品久久久久婷婷 | 日日天天| 国产成人香蕉 | 中文字幕亚洲高清 | 日本三级中文字幕在线观看 | 91在线视频精品 | 国产视频精选在线 | 91视频高清完整版 | 日韩美女免费线视频 | av电影中文字幕 | www.国产毛片 | 韩国精品在线 | 一级精品视频在线观看宜春院 | 国产做aⅴ在线视频播放 | 久久理论电影网 | 美女福利视频 | 国产成人黄色 | 日韩欧美视频在线播放 | 免费v片 | 黄色三级免费网址 | 久久国产片 | 婷婷丁香激情 | mm1313亚洲精品国产 | 中文字幕在线播放av | 一本之道乱码区 | 黄色av网站在线观看免费 | 夜夜爽天天爽 | 9色在线视频 | 国产成人一二三 | 国产精品久久久区三区天天噜 | 91欧美国产 | 99热日本| 欧美极度另类性三渗透 | 亚洲在线视频网站 | 亚洲成人免费在线 | 99精品热视频只有精品10 | 最新三级在线 | 99精品一区二区三区 | 99久久精品无码一区二区毛片 | 99久久99久久精品国产片 | 久久资源在线 | 久久 亚洲视频 | 日韩va欧美va亚洲va久久 | 高清精品在线 | 国产美女精品视频免费观看 | 国产免费高清视频 | 中日韩在线视频 | 狠狠的日 | 亚洲综合在线视频 | av大全免费在线观看 | 亚洲国产中文字幕在线观看 | 日本成人中文字幕在线观看 | 狠狠躁日日躁夜夜躁av | 国产在线观看免费观看 | 久草剧场 | 日韩一级电影在线观看 | 成人黄色影片在线 | 天天干天天射天天操 | 中文不卡视频在线 | 综合激情av| 精品 一区 在线 | 亚洲免费资源 | 最新色视频 | 国产手机视频精品 | 日日草视频 | 日韩区在线观看 | 中文字幕在线观看视频一区二区三区 | 日日干精品 | 一区 二区电影免费在线观看 | 西西444www大胆无视频 | 欧美日韩免费网站 | 亚洲女人av| 91亚洲视频在线观看 | 免费看的黄色的网站 | 国产在线a不卡 | 亚洲精品玖玖玖av在线看 | 亚洲高清视频一区二区三区 | 一级特黄aaa大片在线观看 | 最新黄色av网址 | 中文字幕在线观看完整版电影 | 亚洲精品在线一区二区 | 久久人人97超碰精品888 | 亚洲美女视频在线观看 | 免费黄色一区 | 五月婷久久| 黄色成人毛片 | 久久精品亚洲综合专区 | 国产精品久久久久久久免费大片 | 国产黄色精品 | 99久久er热在这里只有精品15 | 国产亚洲精品久久 | 中文字幕 第二区 | 99re视频在线观看 | 精品美女在线观看 | 日韩精品在线免费播放 | 国产二区视频在线观看 | 97电影网手机版 | 国产精品视频你懂的 | 色老板在线视频 | 亚洲激情 欧美激情 | 免费看国产曰批40分钟 | 亚洲第一av在线 | 国产精品久久久999 国产91九色视频 | 亚洲国产三级在线 | 人人澡人人草 | 国产在线观看地址 | 亚州av一区| 久久1电影院 | 蜜臀久久99精品久久久酒店新书 | 国产精品大片在线观看 | 成人高清av在线 | 综合精品久久 | 韩国av免费观看 | 中文字幕视频播放 | 99色人| 婷婷丁香色 | 不卡的av在线播放 | 精品国产伦一区二区三区观看方式 | 免费观看成年人视频 | 午夜av免费观看 | 亚洲视频电影在线 | 日韩中文字幕第一页 | 中文字幕在线观看1 | 蜜臀av免费一区二区三区 | 亚洲日本va中文字幕 | 91免费网址 | 蜜桃视频精品 | 亚洲黄色激情小说 | 少妇搡bbbb搡bbb搡忠贞 | 国产在线91在线电影 | 久久99久久99精品 | 黄网在线免费观看 | 黄色大片日本免费大片 | 国产手机在线精品 | 国产 欧美 日产久久 | 韩国av一区二区 | 欧美激情第八页 | 亚洲欧美精品一区二区 | 国产精品久久久久久av | 国内精品久久久久久久97牛牛 | 国产精品久久久久永久免费看 | 91精品视频免费观看 | 欧美三人交 | av超碰在线 | 国产精品一区二区三区在线播放 | 欧洲性视频 | 国产香蕉av | 日韩日韩日韩日韩 | 日本久久免费电影 | 色综合色综合色综合 | 日韩av在线影视 | 天天操夜夜操天天射 | 久久国产精品免费一区二区三区 | 丁香九月婷婷综合 | 91精品国产欧美一区二区 | 久久久久亚洲国产 | 中文字幕丝袜一区二区 | 91成人短视频在线观看 | 色欧美88888久久久久久影院 | 99r在线视频 | 国产精品综合久久 | 成人在线播放av | 我要色综合天天 | 成年人黄色av | 中文字幕久久网 | 亚洲男模gay裸体gay | x99av成人免费| 在线观看黄色av | 国产手机在线精品 | 手机在线永久免费观看av片 | 性色视频在线 | 亚洲精品国产成人av在线 | 99精品一区 | 欧洲精品久久久久毛片完整版 | 亚洲最新在线 | ,午夜性刺激免费看视频 | 在线黄色观看 | 热re99久久精品国产66热 | 黄色动态图xx | 欧美日韩有码 | 91麻豆国产 | 欧美综合在线视频 | 午夜丰满寂寞少妇精品 | 波多野结衣视频一区 | 日韩精品91偷拍在线观看 | 天天操·夜夜操 | 91精品免费在线视频 | 17videosex性欧美 | 夜夜夜影院 | 91免费的视频在线播放 | 果冻av在线 | 欧美一区二区免费在线观看 | 五月天伊人 | 91爱爱免费观看 | 不卡精品 | 婷婷色网站 | 亚洲精品在线视频观看 | 永久黄网站色视频免费观看w | 天天操夜夜想 | www.久久久.com| 狠狠网亚洲精品 | av电影免费在线看 | 日日精品 | 少妇按摩av | 中文亚洲欧美日韩 | 99久久国产免费看 | 精品一区二区三区香蕉蜜桃 | 亚洲免费在线观看视频 | 国产精品久久精品 |