乒乓球基本技術(shù)動(dòng)作口訣
|
一、頭訣 |
1、 特點(diǎn) 球速急、落點(diǎn)長、沖力大,發(fā)至對方右大角或中左位置,對對方威脅較大。
2、 要點(diǎn)①拋球不宜太高;②提高擊球瞬間的揮拍速度;③第一落點(diǎn)要靠近本方臺(tái)面的端線;④擊球點(diǎn)與網(wǎng)同高或稍低于網(wǎng)。
?。ǘ┓词职l(fā)急球與發(fā)急下旋球
1、 特點(diǎn) 球速快、弧線低,前沖大,迫使對方后退接球,有利于搶攻,常與發(fā)急下旋球配合使用。
2、 要點(diǎn)①擊球點(diǎn)應(yīng)在身體的左前側(cè)與網(wǎng)同高或比網(wǎng)稍低;②注意手腕的抖動(dòng)發(fā)力;③第一落點(diǎn)在本方臺(tái)區(qū)的端線附近。
?。ㄈ┌l(fā)短球
1、 特點(diǎn) 擊球動(dòng)作小,出手快,球落到對方臺(tái)面后的第二跳下不出臺(tái),使對方不易發(fā)力搶拉、沖或搶攻。
2、 要點(diǎn) ①拋球不宜太高;②擊球時(shí),手腕的力量大于前臂的力量;③發(fā)球的第一落點(diǎn)在球臺(tái)中區(qū),不要離網(wǎng)太近;④發(fā)球動(dòng)作盡可能與發(fā)長球相似,使對方不易判斷。
?。ㄋ模┱职l(fā)轉(zhuǎn)與不轉(zhuǎn)球
1、 特點(diǎn) 球速較慢,前沖力小,主要用相似的發(fā)球動(dòng)作,制造旋轉(zhuǎn)變化去迷惑對方,造成對方接發(fā)球失誤或?yàn)樽约簱尮?chuàng)造機(jī)會(huì)。
2、 要點(diǎn)①拋球不宜太高;②發(fā)轉(zhuǎn)球時(shí),拍面稍后抑,切球的中下部;越是加轉(zhuǎn)球,越應(yīng)注意手臂的前送動(dòng)作;③發(fā)不轉(zhuǎn)球時(shí),擊球瞬間減小拍面后仰角度,增加前推的力量。
(五)正手發(fā)左側(cè)上(下)旋球
1、 特點(diǎn) 左側(cè)上(下)旋轉(zhuǎn)力較強(qiáng),對方擋球時(shí)向其右側(cè)上(下)方反彈,一般站在中線偏左或側(cè)身發(fā)球。
2、 要點(diǎn):①發(fā)球時(shí)要收腹,擊球點(diǎn)不可遠(yuǎn)離身體;②盡量加大由右向左揮動(dòng)的幅度和弧線,以增強(qiáng)側(cè)旋強(qiáng)度。③發(fā)左側(cè)上旋時(shí),擊球瞬間手腕快速內(nèi)收,球拍從球的正中向左上方摩擦。④發(fā)左側(cè)下旋時(shí),拍面稍后仰,球拍從球的中下部向左下方摩擦。
(六) 反手發(fā)右側(cè)上(下)旋球
1. 特點(diǎn) 右側(cè)上(下)旋球力強(qiáng),對方擋住后,向其左側(cè)上(下)反彈。發(fā)球落點(diǎn)以左方斜線長球配合中右近網(wǎng)短球?yàn)榧选?
2. 要點(diǎn) ①注意收腹和轉(zhuǎn)腰動(dòng)作;②充分利用手腕轉(zhuǎn)動(dòng)配合前臂發(fā)力;③發(fā)右側(cè)上旋球時(shí),擊球瞬間球拍從球的中部向右上方摩擦,手腕有一個(gè)上勾動(dòng)作;④發(fā)右側(cè)下旋球時(shí),拍面稍后仰,擊球瞬間球拍從球的中下部向右側(cè)下摩擦。
?。ㄆ撸┫露装l(fā)球
1.特點(diǎn) 下蹲發(fā)球?qū)儆谏鲜诸惏l(fā)球,我國運(yùn)動(dòng)員早在50年代就開始使用。橫拍選手發(fā)下蹲球比直拍選手方便些,直拍選手發(fā)球時(shí)需變化握拍方法,即將食指移放到球拍的背面。下蹲發(fā)球可以發(fā)出左側(cè)旋和右側(cè)旋,在對方不適應(yīng)的情況下,威脅很大,關(guān)鍵時(shí)候發(fā)出高質(zhì)量的球,往往能直接得分。
2. 要點(diǎn)①注意拋球和揮拍擊球動(dòng)作的配合,掌握好擊球時(shí)間。②發(fā)球要有質(zhì)量,發(fā)球動(dòng)作要利落,以防在還未完全站起時(shí)已被對方搶攻③發(fā)下蹲右側(cè)上、下旋球時(shí),左腳稍前,身體略向右偏轉(zhuǎn),揮拍路線為從左后方向右前方。拍觸球中部向右側(cè)上摩擦為右側(cè)上旋;從球中下部向右側(cè)下摩擦為右側(cè)下旋。④發(fā)下蹲左側(cè)上、下旋球時(shí),站位稍平,身體基本正對球臺(tái),揮拍路線為從右后方向左前方。拍觸球右中部向左上方摩擦為左側(cè)上旋;從球中部向左下部摩擦為左側(cè)下旋。⑤發(fā)左(右)側(cè)上、下旋球時(shí),要特別注意快速做半圓形摩擦球的動(dòng)作。
?。ò耍┱指邟伆l(fā)球
1、 特點(diǎn) 最顯著的特點(diǎn)是拋球高,增大了球下降時(shí)對拍的正壓力,發(fā)出的球速度快,沖力大,旋轉(zhuǎn)變化多,著臺(tái)后拐彎飛行。但高拋發(fā)球動(dòng)作復(fù)雜,有一定的難度。
2、 要點(diǎn):①拋球勿離臺(tái)及身體太遠(yuǎn)。②擊球點(diǎn)與網(wǎng)同高或比網(wǎng)稍低,在近腰的中右處(15厘米)為好③盡量加大向內(nèi)擺動(dòng)的幅度和弧線。④發(fā)左側(cè)上、下旋球與低拋發(fā)球同。⑤觸球后,附加一個(gè)向右前方的回收動(dòng)作,可增加對方的判斷(結(jié)合發(fā)右側(cè)旋球,更有威力)。
仔細(xì)觀察鮑羅斯的反手拉球,大致上主要用于以下三種情況:一是反手位的第一板上手,二是正手變反手位時(shí),三是反手位的機(jī)會(huì)比較好時(shí)。而這三種球也恰恰是中國運(yùn)動(dòng)員,特別是水平不高的運(yùn)動(dòng)員常感到很別扭的球。
中國隊(duì)的打法歷來講究積極主動(dòng),體現(xiàn)在打法上的最明顯特征就是突出正手單面進(jìn)攻的使用率和殺傷力。曾有一段時(shí)間,歐洲人在比較清楚地把握了中國人的慣性套路后,以接發(fā)球“晃撇”反手底線長球和“調(diào)右壓左”,從正手突破的戰(zhàn)術(shù),一度使中國隊(duì)陷入非常被動(dòng)的境地,常常令中國隊(duì)“撥”不出手來,難以形成有效的正手單面攻,造成失利。所以,無論是橫板快攻還是弧圈打法,在訓(xùn)練過程中,掌握一板比較高質(zhì)量的反手拉是非常有必要的,可以為發(fā)揮正手的殺傷力創(chuàng)造更多的機(jī)會(huì)。
談橫板反手拉球,不能不說一說它的基本要領(lǐng)。常有業(yè)余愛好者問,正手拉球時(shí),一腳在前,一腳在后,那么反手拉球時(shí),是否需要進(jìn)行重心交換呢?可以說,其基本的道理是差不多的。但反手拉球由于受到身體解剖結(jié)構(gòu)的限制,不能像正手拉球時(shí)過于側(cè)向的站位,一般要接近于兩腳的平行,只要能夠完成基本的重心交換即可。在拉球引拍時(shí),也不能像正手拉一樣有很大的幅度,適當(dāng)?shù)匕阎怅P(guān)節(jié)抬起來一點(diǎn),以增加手臂的發(fā)力距離,這是與正手拉本質(zhì)的區(qū)別;在接觸球的一瞬間,重心往上撥,盡可能向前發(fā)力,以使球體現(xiàn)足夠的向前質(zhì)量。
在練習(xí)和運(yùn)用反手拉球過程中,要注意,作為具有中國人特點(diǎn)打法的運(yùn)動(dòng)員,其進(jìn)攻和上手的指導(dǎo)思想都應(yīng)建立在正手的基礎(chǔ)上,再運(yùn)用反手,千萬不能形成左來左打,右來右打的打法風(fēng)格,有失積極意義,畢竟正手和反手的殺傷力是不同的,這也是中國隊(duì)在長期的實(shí)踐中摸索的打法風(fēng)格的成功經(jīng)驗(yàn)之一。但作為輔助技術(shù)手段,鮑羅斯反手拉球的運(yùn)用和所產(chǎn)生的效果是值得我們借鑒和學(xué)習(xí)的。
可以擴(kuò)展到一切的業(yè)務(wù)對象。
包括基礎(chǔ)方法,操作。
甚至安全對象本身
有點(diǎn)神學(xué)的味道。
可能是因?yàn)椴捎闷渌粍?chuàng)造者不知道的機(jī)制,所以被創(chuàng)造者無法理解和知曉。
您認(rèn)為把 NIO 和 Servlet API 組合在一起是不可能的?請?jiān)俸煤孟胍幌?。在本文中,Java 開發(fā)人員 Taylor Cowan 向您展示了如何把生產(chǎn)者/消費(fèi)者模型應(yīng)用到消費(fèi)者非阻塞 I/O,從而輕松地讓 Servlet API 全新地兼容 NIO。在這個(gè)過程中,您將會(huì)看到采用了什么來創(chuàng)建實(shí)際的基于 Servlet 并實(shí)現(xiàn)了 NIO 的 Web 服務(wù)器;您也將發(fā)現(xiàn)在企業(yè)環(huán)境中,那個(gè)服務(wù)器是如何以標(biāo)準(zhǔn)的 Java I/O 服務(wù)器(Tomcat 5.0)為基礎(chǔ)而創(chuàng)建的。
NIO 是帶有 JDK 1.4 的 Java 平臺(tái)的最有名(如果不是最出色的)的添加部分之一。下面的許多文章闡述了 NIO 的基本知識(shí)及如何利用非阻塞通道的好處。但它們所遺漏的一件事正是,沒有充分地展示 NIO 如何可以提高 J2EE Web 層的可伸縮性。對于企業(yè)開發(fā)人員來說,這些信息特別密切相關(guān),因?yàn)閷?shí)現(xiàn) NIO 不像把少數(shù)幾個(gè) import 語句改變成一個(gè)新的 I/O 包那樣簡單。首先,Servlet API 采用阻塞 I/O 語義,因此默認(rèn)情況下,它不能利用非阻塞 I/O。其次,不像 JDK 1.0 中那樣,線程不再是“資源獨(dú)占”(resource hog),因此使用較少的線程不一定表明服務(wù)器可以處理更多的客戶機(jī)。
在本文中,為了創(chuàng)建基于 Servlet 并實(shí)現(xiàn)了 NIO 的 Web 服務(wù)器,您將學(xué)習(xí)如何解決 Servlet API 與非阻塞 I/O 的不配合問題。我們將會(huì)看到在多元的 Web 服務(wù)器環(huán)境中,這個(gè)服務(wù)器是如何針對標(biāo)準(zhǔn) I/O 服務(wù)器(Tomcat 5.0)進(jìn)行伸縮的。為符合企業(yè)中生存期的事實(shí),我們將重點(diǎn)放在當(dāng)保持 socket 連接的客戶機(jī)數(shù)量以指數(shù)級增長時(shí),NIO 與標(biāo)準(zhǔn) I/O 相比較的情況如何。
注意,本文針對某些 Java 開發(fā)人員,他們已經(jīng)熟悉了 Java 平臺(tái)上 I/O 編程的基礎(chǔ)知識(shí)。有關(guān)非阻塞 I/O 的介紹,請參閱 參考資料 部分。
大家都知道,線程是比較昂貴的。在 Java 平臺(tái)的早期(JDK 1.0),線程的開銷是一個(gè)很大負(fù)擔(dān),因此強(qiáng)制開發(fā)人員自定義生成解決方案。一個(gè)常見的解決方案是使用 VM 啟動(dòng)時(shí)創(chuàng)建的線程池,而不是按需創(chuàng)建每個(gè)新線程。盡管最近在 VM 層上提高了線程的性能,但標(biāo)準(zhǔn) I/O 仍然要求分配惟一的線程來處理每個(gè)新打開的 socket。就短期而言,這工作得相當(dāng)不錯(cuò),但當(dāng)線程的數(shù)量增加超過了 1K,標(biāo)準(zhǔn) I/O 的不足就表現(xiàn)出來了。由于要在線程間進(jìn)行上下文切換,因此 CPU 簡直變成了超載。
由于 JDK 1.4 中引入了 NIO,企業(yè)開發(fā)人員最終有了“單線程”模型的一個(gè)內(nèi)置解決方案:多元 I/O 使得固定數(shù)量的線程可以服務(wù)不斷增長的用戶數(shù)量。
多路復(fù)用(Multiplexing)指的是通過一個(gè)載波來同時(shí)發(fā)送多個(gè)信號(hào)或流。當(dāng)使用手機(jī)時(shí),日常的多路復(fù)用例子就發(fā)生了。無線頻率是稀有的資源,因此無線頻率提供商使用多路復(fù)用技術(shù)通過一個(gè)頻率發(fā)送多個(gè)呼叫。在一個(gè)例子中,把呼叫分成一些段,然后給這些段很短的持續(xù)時(shí)間,并在接收端重新裝配。這就叫做 時(shí)分多路復(fù)用(time-division multiplexing),即 TDM。
在 NIO 中,接收端相當(dāng)于“選擇器”(參閱 java.nio.channels.Selector
)。不是處理呼叫,選擇器是處理多個(gè)打開的 socket。就像在 TDM 中那樣,選擇器重新裝配從多個(gè)客戶機(jī)寫入的數(shù)據(jù)段。這使得服務(wù)器可以用單個(gè)線程管理多個(gè)客戶機(jī)。
![]() ![]() |
![]()
|
對于 NIO,非阻塞讀寫是必要的,但它們并不是完全沒有麻煩。除了不會(huì)阻塞之外,非阻塞讀不能給呼叫方任何保證??蛻魴C(jī)或服務(wù)器應(yīng)用程序可能讀取完整信息、部分消息或者根本讀取不到消息。另外,非阻塞讀可能讀取到太多的消息,從而強(qiáng)制為下一個(gè)呼叫準(zhǔn)備一個(gè)額外的緩沖區(qū)。最后,不像流那樣,讀取了零字節(jié)并不表明已經(jīng)完全接收了消息。
這些因素使得沒有輪詢就不可能實(shí)現(xiàn)甚至是簡單的 readline
方法。所有的 servlet 容器必須在它們的輸入流上提供 readline
方法。因此,許多開發(fā)人員放棄了創(chuàng)建基于 Servlet 并實(shí)現(xiàn)了 NIO 的 Web 應(yīng)用程序服務(wù)器。不過這里有一個(gè)解決方案,它組合了 Servlet API 和 NIO 的多元 I/O 的能力。
在下面的幾節(jié)中,您將學(xué)習(xí)如何使用 java.io.PipedInput
和 PipedOutputStream
類來把生產(chǎn)者/消費(fèi)者模型應(yīng)用到消費(fèi)者非阻塞 I/O。當(dāng)讀取非阻塞通道時(shí),把它寫到正由第二個(gè)線程消費(fèi)的管道。注意,這種分解映射線程不同于大多數(shù)基于 Java 的客戶機(jī)/服務(wù)器應(yīng)用程序。這里,我們讓一個(gè)線程單獨(dú)負(fù)責(zé)處理非阻塞通道(生產(chǎn)者),讓另一個(gè)線程單獨(dú)負(fù)責(zé)把數(shù)據(jù)作為流消費(fèi)(消費(fèi)者)。管道也為應(yīng)用程序服務(wù)器解決了非阻塞 I/O 問題,因?yàn)?servlet 在消費(fèi) I/O 時(shí)將采用阻塞語義。
![]() ![]() |
![]()
|
示例服務(wù)器展示了 Servlet API 和 NIO 不兼容的生產(chǎn)者/消費(fèi)者解決方案。該服務(wù)器與 Servlet API 非常相似,可以為成熟的基于 NIO 應(yīng)用程序服務(wù)器提供 POC (proof of concept),是專門編寫來衡量 NIO 相對于標(biāo)準(zhǔn) Java I/O 的性能的。它處理簡單的 HTTP get
請求,并支持來自客戶機(jī)的 Keep-Alive 連接。這是重要的,因?yàn)槎嗦窂?fù)用 I/O 只證明在要求服務(wù)器處理大量打開的 scoket 連接時(shí)是有意的。
該服務(wù)器被分成兩個(gè)包: org.sse.server
和 org.sse.http
包中有提供主要 服務(wù)器
功能的類,比如如下的一些功能:接收新客戶機(jī)連接、閱讀消息和生成工作線程以處理請求。 http
包支持 HTTP 協(xié)議的一個(gè)子集。詳細(xì)闡述 HTTP 超出了本文的范圍。有關(guān)實(shí)現(xiàn)細(xì)節(jié),請從 參考資料 部分下載代碼示例。
現(xiàn)在讓我們來看一下 org.sse.server
包中一些最重要的類。
![]() ![]() |
![]()
|
Server
類擁有多路復(fù)用循環(huán) —— 任何基于 NIO 服務(wù)器的核心。在清單 1 中,在服務(wù)器接收新客戶機(jī)或檢測到正把可用的字節(jié)寫到打開的 socket 前, select()
的調(diào)用阻塞了。這與標(biāo)準(zhǔn) Java I/O 的主要區(qū)別是,所有的數(shù)據(jù)都是在這個(gè)循環(huán)中讀取的。通常會(huì)把從特定 socket 中讀取字節(jié)的任務(wù)分配給一個(gè)新線程。使用 NIO 選擇器事件驅(qū)動(dòng)方法,實(shí)際上可以用單個(gè)線程處理成千上萬的客戶機(jī),不過,我們還會(huì)在后面看到線程仍有一個(gè)角色要扮演。
每個(gè) select()
調(diào)用返回一組事件,指出新客戶機(jī)可用;新數(shù)據(jù)準(zhǔn)備就緒,可以讀取;或者客戶機(jī)準(zhǔn)備就緒,可以接收響應(yīng)。server 的 handleKey()
方法只對新客戶機(jī)( key.isAcceptable()
)和傳入數(shù)據(jù) ( key.isReadable()
) 感興趣。到這里,工作就結(jié)束了,轉(zhuǎn)入 ServerEventHandler
類。
清單 1. Server.java 選擇器循環(huán)
public void listen() { SelectionKey key = null; try { while (true) { selector.select(); Iterator it = selector.selectedKeys().iterator(); while (it.hasNext()) { key = (SelectionKey) it.next(); handleKey(key); it.remove(); } } } catch (IOException e) { key.cancel(); } catch (NullPointerException e) { // NullPointer at sun.nio.ch.WindowsSelectorImpl, Bug: 4729342 e.printStackTrace(); } } |
![]() ![]() |
![]()
|
ServerEventHandler
類響應(yīng)服務(wù)器事件。當(dāng)新客戶機(jī)變?yōu)榭捎脮r(shí),它就實(shí)例化一個(gè)新的 Client
對象,該對象代表了那個(gè)客戶機(jī)的狀態(tài)。數(shù)據(jù)是以非阻塞方式從通道中讀取的,并被寫到 Client
對象中。 ServerEventHandler
對象也維護(hù)請求隊(duì)列。為了處理(消費(fèi))隊(duì)列中的請求,生成了不定數(shù)量的工作線程。在傳統(tǒng)的生產(chǎn)者/消費(fèi)者方式下,為了在隊(duì)列變?yōu)榭諘r(shí)線程會(huì)阻塞,并在新請求可用時(shí)線程會(huì)得到通知,需要寫 Queue
。
為了支持等待的線程,在清單 2 中已經(jīng)重寫了 remove()
方法。如果列表為空,就會(huì)增加等待線程的數(shù)量,并阻塞當(dāng)前線程。它實(shí)質(zhì)上提供了非常簡單的線程池。
清單 2. Queue.java
public class Queue extends LinkedList { private int waitingThreads = 0; public synchronized void insert(Object obj) { addLast(obj); notify(); } public synchronized Object remove() { if ( isEmpty() ) { try { waitingThreads++; wait();} catch (InterruptedException e) {Thread.interrupted();} waitingThreads--; } return removeFirst(); } public boolean isEmpty() { return (size() - waitingThreads <= 0); } } |
工作線程的數(shù)量與 Web 客戶機(jī)的數(shù)量無關(guān)。不是為每個(gè)打開的 socket 分配一個(gè)線程,相反,我們把所有請求放到一個(gè)由一組 RequestHandlerThread
實(shí)例所服務(wù)的通用隊(duì)列中。理想情況下,線程的數(shù)量應(yīng)該根據(jù)處理器的數(shù)量和請求的長度或持續(xù)時(shí)間進(jìn)行調(diào)整。如果請求通過資源或處理需求花了很長時(shí)間,那么通過添加更多的線程,可以提高感知到的服務(wù)質(zhì)量。
注意,這不一定提高整體的吞吐量,但確實(shí)改善了用戶體驗(yàn)。即使在超載的情況下,也會(huì)給每個(gè)線程一個(gè)處理時(shí)間片。這一原則同樣適用于基于標(biāo)準(zhǔn) Java I/O 的服務(wù)器;不過這些服務(wù)器是受到限制的,因?yàn)闀?huì) 要求 它們?yōu)槊總€(gè)打開的 socket 連接分配一個(gè)線程。NIO 服務(wù)器完全不用擔(dān)心這一點(diǎn),因此它們可以擴(kuò)展到大量用戶。最后的結(jié)果是 NIO 服務(wù)器仍然需要線程,只是不需要那么多。
![]() ![]() |
![]()
|
Client
類有兩個(gè)用途。首先,通過把傳入的非阻塞 I/O 轉(zhuǎn)換成可由 Servlet API 消費(fèi)的阻塞 InputStream
,它解決了阻塞/非阻塞問題。其次,它管理特定客戶機(jī)的請求狀態(tài)。因?yàn)楫?dāng)全部讀取消息時(shí),非阻塞通道沒有給出任何提示,所以強(qiáng)制我們在協(xié)議層處理這一情況。 Client
類在任意指定的時(shí)刻都指出了它是否正在參與進(jìn)行中的請求。如果它準(zhǔn)備處理新請求, write()
方法就會(huì)為請求處理而將該客戶機(jī)排到隊(duì)列中。如果它已經(jīng)參與了請求,它就只是使用 PipedInputStream
和 PipedOutputStream
類把傳入的字節(jié)轉(zhuǎn)換成一個(gè) InputStream
。
圖 1 展示了兩個(gè)線程圍繞管道進(jìn)行交互。主線程把從通道讀取的數(shù)據(jù)寫到管道中。管道把相同的數(shù)據(jù)作為 InputStream
提供給消費(fèi)者。管道的另一個(gè)重要特性是:它是進(jìn)行緩沖處理的。如果沒有進(jìn)行緩沖處理,主線程在嘗試寫到管道時(shí)就會(huì)阻塞。因?yàn)橹骶€程單獨(dú)負(fù)責(zé)所有客戶機(jī)間的多路復(fù)用,因此我們不能讓它阻塞。
圖 1. PipedInput/OutputStream

在 Client
自己排隊(duì)后,工作線程就可以消費(fèi)它了。 RequestHandlerThread
類承擔(dān)了這個(gè)角色。至此,我們已經(jīng)看到主線程是如何連續(xù)地循環(huán)的,它要么接受新客戶機(jī),要么讀取新的 I/O。工作線程循環(huán)等待新請求。當(dāng)客戶機(jī)在請求隊(duì)列上變?yōu)榭捎脮r(shí),它就馬上被 remove()
方法中阻塞的第一個(gè)等待線程所消費(fèi)。
清單 3. RequestHandlerThread.java
public void run() { while (true) { Client client = (Client) myQueue.remove(); try { for (; ; ) { HttpRequest req = new HttpRequest(client.clientInputStream, myServletContext); HttpResponse res = new HttpResponse(client.key); defaultServlet.service(req, res); if (client.notifyRequestDone()) break; } } catch (Exception e) { client.key.cancel(); client.key.selector().wakeup(); } } } |
然后該線程創(chuàng)建新的 HttpRequest
和 HttpResponse
實(shí)例,并調(diào)用 defaultServlet
的 service 方法。注意, HttpRequest
是用 Client
對象的 clientInputStream
屬性構(gòu)造的。 PipedInputStream
就是負(fù)責(zé)把非阻塞 I/O 轉(zhuǎn)換成阻塞流。
從現(xiàn)在開始,請求處理就與您在 J2EE Servlet API 中期望的相似。當(dāng)對 servlet 的調(diào)用返回時(shí),工作線程在返回到池中之前,會(huì)檢查是否有來自相同客戶機(jī)的另一個(gè)請求可用。注意,這里用到了單詞 池 (pool)。事實(shí)上,線程會(huì)對隊(duì)列嘗試另一個(gè) remove()
調(diào)用,并變成阻塞,直到下一個(gè)請求可用。
![]() ![]() |
![]()
|
示例服務(wù)器實(shí)現(xiàn)了 HTTP 1.1 協(xié)議的一個(gè)子集。它處理普通的 HTTP get
請求。它帶有兩個(gè)命令行參數(shù)。第一個(gè)指定端口號(hào),第二個(gè)指定 HTML 文件所駐留的目錄。在解壓文件后, 切換到項(xiàng)目目錄,然后執(zhí)行下面的命令,注意要把下面的 webroot 目錄替換為您自己的目錄:
java -cp bin org.sse.server.Start 8080 "C:\mywebroot" |
還請注意,服務(wù)器并沒有實(shí)現(xiàn)目錄清單,因此必須指定有效的 URL 來指向您的 webroot 目錄下的文件。
![]() ![]() |
![]()
|
示例 NIO 服務(wù)器是在重負(fù)載下與 Tomcat 5.0 進(jìn)行比較的。選擇 Tomcat 是因?yàn)樗腔跇?biāo)準(zhǔn) Java I/O 的純 Java 解決方案。為了提高可伸縮性,一些高級的應(yīng)用程序服務(wù)器是用 JNI 本機(jī)代碼優(yōu)化的,因此它們沒有提供標(biāo)準(zhǔn) I/O 和 NIO 之間的很好比較。目標(biāo)是要確定 NIO 是否給出了大量的性能優(yōu)勢,以及是在什么條件下給出的。
如下是一些說明:
- Tomcat 是用最大的線程數(shù)量 2000 來配置的,而示例服務(wù)器只允許用 4 個(gè)工作線程運(yùn)行。
- 每個(gè)服務(wù)器是針對相同的一組簡單 HTTP
get
測試的,這些 HTTPget
基本上由文本內(nèi)容組成。
- 把加載工具(Microsoft Web Application Stress Tool)設(shè)置為使用“Keep-Alive”會(huì)話,導(dǎo)致了大約要為每個(gè)用戶分配一個(gè) socket。然后它導(dǎo)致了在 Tomcat 上為每個(gè)用戶分配一個(gè)線程,而 NIO 服務(wù)器用固定數(shù)量的線程來處理相同的負(fù)載。
圖 2 展示了在不斷增加負(fù)載下的“請求/秒”率。在 200 個(gè)用戶時(shí),性能是相似的。但當(dāng)用戶數(shù)量超過 600 時(shí),Tomcat 的性能開始急劇下降。這最有可能是由于在這么多的線程間切換上下文的開銷而導(dǎo)致的。相反,基于 NIO 的服務(wù)器的性能則以線性方式下降。記住,Tomcat 必須為每個(gè)用戶分配一個(gè)線程,而 NIO 服務(wù)器只配置有 4 個(gè)工作線程。
圖 2. 請求/秒

圖 3 進(jìn)一步顯示了 NIO 的性能。它展示了操作的 Socket 連接錯(cuò)誤數(shù)/分鐘。同樣,在大約 600 個(gè)用戶時(shí),Tomcat 的性能急劇下降,而基于 NIO 的服務(wù)器的錯(cuò)誤率保持相對較低。
圖 3. Socket 連接錯(cuò)誤數(shù)/分鐘

![]() ![]() |
![]()
|
在本文中您已經(jīng)學(xué)習(xí)了,實(shí)際上可以使用 NIO 編寫基于 Servlet 的 Web 服務(wù)器,甚至可以啟用它的非阻塞特性。對于企業(yè)開發(fā)人員來說,這是好消息,因?yàn)樵谄髽I(yè)環(huán)境中,NIO 比標(biāo)準(zhǔn) Java I/O 更能夠進(jìn)行伸縮。不像標(biāo)準(zhǔn)的 Java I/O,NIO 可以用固定數(shù)量的線程處理許多客戶機(jī)。當(dāng)基于 Servlet 的 NIO Web 服務(wù)器用來處理保持和擁有 socket 連接的客戶機(jī)時(shí),會(huì)獲得更好的性能。
- 您可以參閱本文在 developerWorks 全球站點(diǎn)上的 英文原文.
- 下載本文中使用的 源代碼。
- 看看“ Merlin 給 Java 平臺(tái)帶來了非阻塞 I/O”( developerWorks,2002 年 3 月),獲得 NIO 語義的進(jìn)一步知識(shí)。
- 綜合的 developerWorks 教程“ NIO 入門”( developerWorks,2003 年 11 月)從高級的概念到底層的編程細(xì)節(jié),詳細(xì)論及了 NIO 庫。
- Merlin Hughes 的由兩部分組成的文章“ Turning streams inside out”( developerWorks,2002 年 9 月)為 Java I/O(標(biāo)準(zhǔn)版和 NIO 版)的一些普遍的挑戰(zhàn)提供了制作精巧的設(shè)計(jì)解決方案。
- 為獲取有關(guān) Java I/O 問題的一些背景知識(shí),請參閱 Allen Holub 的“ 關(guān)于解決 Java 編程語言線程問題的建議 ”( developerWorks,2000 年 10 月)。
- 訪問 NIO 主頁,從資源中學(xué)習(xí)非阻塞 I/O。
- JavaNIO.info 是查找有關(guān) NIO 的資源的理想地方。
- 為從書本系統(tǒng)學(xué)習(xí) NIO,請參閱該領(lǐng)域的經(jīng)典著作:Ron Hitchens 的 Java NIO(O'Reilly & Associates,2002 年)。
- 在 developerWorks Java 技術(shù)專區(qū)可以找到有關(guān) Java 編程各個(gè)方面的文章。
- 訪問 Developer Bookstore,獲取技術(shù)書籍的完整列表,其中包括數(shù)百本 Java 相關(guān)的圖書。
- 也請參閱 Java 技術(shù)專區(qū)教程頁,從 developerWorks 獲取免費(fèi)的 Java 專門教程的完整列表。
![]() |
||
|
![]() |
Taylor Cowan 是一位軟件工程師,也是一位專攻 J2EE 的自由撰稿人。他從 North Texas 大學(xué)的計(jì)算機(jī)科學(xué)專業(yè)獲得了碩士學(xué)位,另外,他還從 Jazz Arranging 獲得了音樂學(xué)士學(xué)位。 |
一種是繼承自Thread類.Thread 類是一個(gè)具體的類,即不是抽象類,該類封裝了線程的行為。要?jiǎng)?chuàng)建一個(gè)線程,程序員必須創(chuàng)建一個(gè)從 Thread 類導(dǎo)出的新類。程序員通過覆蓋 Thread 的 run() 函數(shù)來完成有用的工作。用戶并不直接調(diào)用此函數(shù);而是通過調(diào)用 Thread 的 start() 函數(shù),該函數(shù)再調(diào)用 run()。
例如:
public class Test extends Thread{
public Test(){
}
public static void main(String args[]){
Test t1 = new Test();
Test t2 = new Test();
t1.start();
t2.start();
}
public void run(){
//do thread's things
}
}
另一種是實(shí)現(xiàn)Runnable接口,此接口只有一個(gè)函數(shù),run(),此函數(shù)必須由實(shí)現(xiàn)了此接口的類實(shí)現(xiàn)。
例如:
public class Test implements Runnable{
Thread thread1;
Thread thread2;
public Test(){
thread1 = new Thread(this,"1");
thread2 = new Thread(this,"2");
}
public static void main(String args[]){
Test t = new Test();
t.startThreads();
}
public void run(){
//do thread's things
}
public void startThreads(){
thread1.start();
thread2.start();
}
}
兩種創(chuàng)建方式看起來差別不大,但是弄不清楚的話,也許會(huì)將你的程序弄得一團(tuán)糟。兩者區(qū)別有以下幾點(diǎn):
1.當(dāng)你想繼承某一其它類時(shí),你只能用后一種方式.
2.第一種因?yàn)槔^承自Thread,只創(chuàng)建了自身對象,但是在數(shù)量上,需要幾個(gè)線程,就得創(chuàng)建幾個(gè)自身對象;第二種只創(chuàng)建一個(gè)自身對象,卻創(chuàng)建幾個(gè)Thread對象.而兩種方法重大的區(qū)別就在于此,請你考慮:如果你在第一種里創(chuàng)建數(shù)個(gè)自身對象并且start()后,你會(huì)發(fā)現(xiàn)好像synchronized不起作用了,已經(jīng)加鎖的代碼塊或者方法居然同時(shí)可以有幾個(gè)線程進(jìn)去,而且同樣一個(gè)變量,居然可以有好幾個(gè)線程同時(shí)可以去更改它。(例如下面的代碼)這是因?yàn)椋谶@個(gè)程序中,雖然你起了數(shù)個(gè)線程,可是你也創(chuàng)建了數(shù)個(gè)對象,而且,每個(gè)線程對應(yīng)了每個(gè)對象也就是說,每個(gè)線程更改和占有的對象都不一樣,所以就出現(xiàn)了同時(shí)有幾個(gè)線程進(jìn)入一個(gè)方法的現(xiàn)象,其實(shí),那也不是一個(gè)方法,而是不同對象的相同的方法。所以,這時(shí)候你要加鎖的話,只能將方法或者變量聲明為靜態(tài),將static加上后,你就會(huì)發(fā)現(xiàn),線程又能管住方法了,同時(shí)不可能有兩個(gè)線程進(jìn)入同樣一個(gè)方法,那是因?yàn)?,現(xiàn)在不是每個(gè)對象都擁有一個(gè)方法了,而是所有的對象共同擁有一個(gè)方法,這個(gè)方法就是靜態(tài)方法。
而你如果用第二種方法使用線程的話,就不會(huì)有上述的情況,因?yàn)榇藭r(shí),你只創(chuàng)建了一個(gè)自身對象,所以,自身對象的屬性和方法對于線程來說是共有的。
因此,我建議,最好用后一種方法來使用線程。
public class mainThread extends Thread{
int i=0;
public static void main(String args[]){
mainThread m1 = new mainThread();
mainThread m2 = new mainThread();
mainThread m3 = new mainThread();
mainThread m4 = new mainThread();
mainThread m5 = new mainThread();
mainThread m6 = new mainThread();
m1.start();
m2.start();
m3.start();
m4.start();
m5.start();
m6.start();
}
public synchronized void t1(){
i=++i;
try{
Thread.sleep(500);
}
catch(Exception e){}
//每個(gè)線程都進(jìn)入各自的t1()方法,分別打印各自的i
System.out.println(Thread.currentThread().getName()+" "+i);
}
public void run(){
synchronized(this){
while (true) {
t1();
}
}
}
}
下面我們來講synchronized的4種用法吧:
1.方法聲明時(shí)使用,放在范圍操作符(public等)之后,返回類型聲明(void等)之前.即一次只能有一個(gè)線程進(jìn)入該方法,其他線程要想在此時(shí)調(diào)用該方法,只能排隊(duì)等候,當(dāng)前線程(就是在synchronized方法內(nèi)部的線程)執(zhí)行完該方法后,別的線程才能進(jìn)入.
例如:
public synchronized void synMethod() {
//方法體
}
2.對某一代碼塊使用,synchronized后跟括號(hào),括號(hào)里是變量,這樣,一次只有一個(gè)線程進(jìn)入該代碼塊.例如:
public int synMethod(int a1){
synchronized(a1) {
//一次只能有一個(gè)線程進(jìn)入
}
}
3.synchronized后面括號(hào)里是一對象,此時(shí),線程獲得的是對象鎖.例如:
public class MyThread implements Runnable {
public static void main(String args[]) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt, "t1");
Thread t2 = new Thread(mt, "t2");
Thread t3 = new Thread(mt, "t3");
Thread t4 = new Thread(mt, "t4");
Thread t5 = new Thread(mt, "t5");
Thread t6 = new Thread(mt, "t6");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}public void run() {
synchronized (this) {
System.out.println(Thread.currentThread().getName());
}
}
}
對于3,如果線程進(jìn)入,則得到對象鎖,那么別的線程在該類所有對象上的任何操作都不能進(jìn)行.在對象級使用鎖通常是一種比較粗糙的方法。為什么要將整個(gè)對象都上鎖,而不允許其他線程短暫地使用對象中其他同步方法來訪問共享資源?如果一個(gè)對象擁有多個(gè)資源,就不需要只為了讓一個(gè)線程使用其中一部分資源,就將所有線程都鎖在外面。由于每個(gè)對象都有鎖,可以如下所示使用虛擬對象來上鎖:
class FineGrainLock {
MyMemberClass x, y;
Object xlock = new Object(), ylock = new Object();public void foo() {
synchronized(xlock) {
//access x here
}//do something here - but don't use shared resources
synchronized(ylock) {
//access y here
}
}public void bar() {
synchronized(this) {
//access both x and y here
}
//do something here - but don't use shared resources
}
}
4.synchronized后面括號(hào)里是類.例如:
class ArrayWithLockOrder{
private static long num_locks = 0;
private long lock_order;
private int[] arr;public ArrayWithLockOrder(int[] a)
{
arr = a;
synchronized(ArrayWithLockOrder.class) {//-----------------------------------------這里
num_locks++; // 鎖數(shù)加 1。
lock_order = num_locks; // 為此對象實(shí)例設(shè)置唯一的 lock_order。
}
}
public long lockOrder()
{
return lock_order;
}
public int[] array()
{
return arr;
}
}class SomeClass implements Runnable
{
public int sumArrays(ArrayWithLockOrder a1,
ArrayWithLockOrder a2)
{
int value = 0;
ArrayWithLockOrder first = a1; // 保留數(shù)組引用的一個(gè)
ArrayWithLockOrder last = a2; // 本地副本。
int size = a1.array().length;
if (size == a2.array().length)
{
if (a1.lockOrder() > a2.lockOrder()) // 確定并設(shè)置對象的鎖定
{ // 順序。
first = a2;
last = a1;
}
synchronized(first) { // 按正確的順序鎖定對象。
synchronized(last) {
int[] arr1 = a1.array();
int[] arr2 = a2.array();
for (int i=0; i<size; i++)
value += arr1[i] + arr2[i];
}
}
}
return value;
}
public void run() {
//...
}
}
對于4,如果線程進(jìn)入,則線程在該類中所有操作不能進(jìn)行,包括靜態(tài)變量和靜態(tài)方法,實(shí)際上,對于含有靜態(tài)方法和靜態(tài)變量的代碼塊的同步,我們通常用4來加鎖.
以上4種之間的關(guān)系:
鎖是和對象相關(guān)聯(lián)的,每個(gè)對象有一把鎖,為了執(zhí)行synchronized語句,線程必須能夠獲得synchronized語句中表達(dá)式指定的對象的鎖,一個(gè)對象只有一把鎖,被一個(gè)線程獲得之后它就不再擁有這把鎖,線程在執(zhí)行完synchronized語句后,將獲得鎖交還給對象。
在方法前面加上synchronized修飾符即可以將一個(gè)方法聲明為同步化方法。同步化方法在執(zhí)行之前獲得一個(gè)鎖。如果這是一個(gè)類方法,那么獲得的鎖是和聲明方法的類相關(guān)的Class類對象的鎖。如果這是一個(gè)實(shí)例方法,那么此鎖是this對象的鎖。
下面談一談一些常用的方法:
wait(),wait(long),notify(),notifyAll()等方法是當(dāng)前類的實(shí)例方法,
wait()是使持有對象鎖的線程釋放鎖;
wait(long)是使持有對象鎖的線程釋放鎖時(shí)間為long(毫秒)后,再次獲得鎖,wait()和wait(0)等價(jià);
notify()是喚醒一個(gè)正在等待該對象鎖的線程,如果等待的線程不止一個(gè),那么被喚醒的線程由jvm確定;
notifyAll是喚醒所有正在等待該對象鎖的線程.
在這里我也重申一下,我們應(yīng)該優(yōu)先使用notifyAll()方法,因?yàn)閱拘阉芯€程比喚醒一個(gè)線程更容易讓jvm找到最適合被喚醒的線程.
對于上述方法,只有在當(dāng)前線程中才能使用,否則報(bào)運(yùn)行時(shí)錯(cuò)誤java.lang.IllegalMonitorStateException: current thread not owner.
下面,我談一下synchronized和wait()、notify()等的關(guān)系:
1.有synchronized的地方不一定有wait,notify
2.有wait,notify的地方必有synchronized.這是因?yàn)閣ait和notify不是屬于線程類,而是每一個(gè)對象都具有的方法,而且,這兩個(gè)方法都和對象鎖有關(guān),有鎖的地方,必有synchronized。
另外,請注意一點(diǎn):如果要把notify和wait方法放在一起用的話,必須先調(diào)用notify后調(diào)用wait,因?yàn)槿绻{(diào)用完wait,該線程就已經(jīng)不是current thread了。如下例:
/**
* Title: Jdeveloper's Java Projdect
* Description: n/a
* Copyright: Copyright (c) 2001
* Company: soho http://www.ChinaJavaWorld.com
* @author jdeveloper@21cn.com
* @version 1.0
*/
import java.lang.Runnable;
import java.lang.Thread;
public class DemoThread
implements Runnable {
public DemoThread() {
TestThread testthread1 = new TestThread(this, "1");
TestThread testthread2 = new TestThread(this, "2");
testthread2.start();
testthread1.start();
}
public static void main(String[] args) {
DemoThread demoThread1 = new DemoThread();
}
public void run() {
TestThread t = (TestThread) Thread.currentThread();
try {
if (!t.getName().equalsIgnoreCase("1")) {
synchronized (this) {
wait();
}
}
while (true) {
System.out.println("@time in thread" + t.getName() + "=" +
t.increaseTime());
if (t.getTime() % 10 == 0) {
synchronized (this) {
System.out.println("****************************************");
notify();
if (t.getTime() == 100)
break;
wait();
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}
class TestThread
extends Thread {
private int time = 0;
public TestThread(Runnable r, String name) {
super(r, name);
}
public int getTime() {
return time;
}
public int increaseTime() {
return++time;
}
}
下面我們用生產(chǎn)者/消費(fèi)者這個(gè)例子來說明他們之間的關(guān)系:
public class test {
public static void main(String args[]) {
Semaphore s = new Semaphore(1);
Thread t1 = new Thread(s, "producer1");
Thread t2 = new Thread(s, "producer2");
Thread t3 = new Thread(s, "producer3");
Thread t4 = new Thread(s, "consumer1");
Thread t5 = new Thread(s, "consumer2");
Thread t6 = new Thread(s, "consumer3");
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
t6.start();
}
}class Semaphore
implements Runnable {
private int count;
public Semaphore(int n) {
this.count = n;
}public synchronized void acquire() {
while (count == 0) {
try {
wait();
}
catch (InterruptedException e) {
//keep trying
}
}
count--;
}public synchronized void release() {
while (count == 10) {
try {
wait();
}
catch (InterruptedException e) {
//keep trying
}
}
count++;
notifyAll(); //alert a thread that's blocking on this semaphore
}public void run() {
while (true) {
if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("consumer")) {
acquire();
}
else if (Thread.currentThread().getName().substring(0,8).equalsIgnoreCase("producer")) {
release();
}
System.out.println(Thread.currentThread().getName() + " " + count);
}
}
}
生產(chǎn)者生產(chǎn),消費(fèi)者消費(fèi),一般沒有沖突,但當(dāng)庫存為0時(shí),消費(fèi)者要消費(fèi)是不行的,但當(dāng)庫存為上限(這里是10)時(shí),生產(chǎn)者也不能生產(chǎn).請好好研讀上面的程序,你一定會(huì)比以前進(jìn)步很多.
上面的代碼說明了synchronized和wait,notify沒有絕對的關(guān)系,在synchronized聲明的方法、代碼塊中,你完全可以不用wait,notify等方法,但是,如果當(dāng)線程對某一資源存在某種爭用的情況下,你必須適時(shí)得將線程放入等待或者喚醒.
2003 年 11 月 24 日
盡管 SSL 阻塞操作――當(dāng)讀寫數(shù)據(jù)的時(shí)候套接字的訪問被阻塞――與對應(yīng)的非阻塞方式相比提供了更好的 I/O 錯(cuò)誤通知,但是非阻塞操作允許調(diào)用的線程繼續(xù)運(yùn)行。本文中,作者同時(shí)就客戶端和服務(wù)器端描述了如何使用Java Secure Socket Extensions (JSSE) 和 Java NIO (新 I/O)庫創(chuàng)建非阻塞的安全連接,并且介紹了創(chuàng)建非阻塞套接字的傳統(tǒng)方法,以及使用JSSE 和 NIO 的一種可選的(必需的)方法。
阻塞,還是非阻塞?這就是問題所在。無論在程序員的頭腦中多么高貴……當(dāng)然這不是莎士比亞,本文提出了任何程序員在編寫 Internet 客戶程序時(shí)都應(yīng)該考慮的一個(gè)重要問題。通信操作應(yīng)該是阻塞的還是非阻塞的?
許多程序員在使用 Java 語言編寫 Internet 客戶程序時(shí)并沒有考慮這個(gè)問題,主要是因?yàn)樵谝郧爸挥幸环N選擇――阻塞通信。但是現(xiàn)在 Java 程序員有了新的選擇,因此我們編寫的每個(gè)客戶程序也許都應(yīng)該考慮一下。
非阻塞通信在 Java 2 SDK 的 1.4 版被引入 Java 語言。如果您曾經(jīng)使用該版本編過程序,可能會(huì)對新的 I/O 庫(NIO)留下了印象。在引入它之前,非阻塞通信只有在實(shí)現(xiàn)第三方庫的時(shí)候才能使用,而第三方庫常常會(huì)給應(yīng)用程序引入缺陷。
NIO 庫包含了文件、管道以及客戶機(jī)和服務(wù)器套接字的非阻塞功能。庫中缺少的一個(gè)特性是安全的非阻塞套接字連接。在 NIO 或者 JSSE 庫中沒有建立安全的非阻塞通道類,但這并不意味著不能使用安全的非阻塞通信。只不過稍微麻煩一點(diǎn)。
要完全領(lǐng)會(huì)本文,您需要熟悉:
- Java 套接字通信的概念。您也應(yīng)該實(shí)際編寫過應(yīng)用程序。而且不只是打開連接、讀取一行然后退出的簡單應(yīng)用程序,應(yīng)該是實(shí)現(xiàn) POP3 或 HTTP 之類協(xié)議的客戶機(jī)或通信庫這樣的程序。
- SSL 基本概念和加密之類的概念。基本上就是知道如何設(shè)置一個(gè)安全連接(但不必?fù)?dān)心 JSSE ――這就是關(guān)于它的一個(gè)“緊急教程”)。
- NIO 庫。
- 在您選擇的平臺(tái)上安裝 Java 2 SDK 1.4 或以后的版本。(我是在 Windows 98 上使用 1.4.1_01 版。)
如果需要關(guān)于這些技術(shù)的介紹,請參閱 參考資料部分。
那么到底什么是阻塞和非阻塞通信呢?
阻塞通信意味著通信方法在嘗試訪問套接字或者讀寫數(shù)據(jù)時(shí)阻塞了對套接字的訪問。在 JDK 1.4 之前,繞過阻塞限制的方法是無限制地使用線程,但這樣常常會(huì)造成大量的線程開銷,對系統(tǒng)的性能和可伸縮性產(chǎn)生影響。java.nio 包改變了這種狀況,允許服務(wù)器有效地使用 I/O 流,在合理的時(shí)間內(nèi)處理所服務(wù)的客戶請求。
沒有非阻塞通信,這個(gè)過程就像我所喜歡說的“為所欲為”那樣?;旧?,這個(gè)過程就是發(fā)送和讀取任何能夠發(fā)送/讀取的東西。如果沒有可以讀取的東西,它就中止讀操作,做其他的事情直到能夠讀取為止。當(dāng)發(fā)送數(shù)據(jù)時(shí),該過程將試圖發(fā)送所有的數(shù)據(jù),但返回實(shí)際發(fā)送出的內(nèi)容??赡苁侨繑?shù)據(jù)、部分?jǐn)?shù)據(jù)或者根本沒有發(fā)送數(shù)據(jù)。
阻塞與非阻塞相比確實(shí)有一些優(yōu)點(diǎn),特別是遇到錯(cuò)誤控制問題的時(shí)候。在阻塞套接字通信中,如果出現(xiàn)錯(cuò)誤,該訪問會(huì)自動(dòng)返回標(biāo)志錯(cuò)誤的代碼。錯(cuò)誤可能是由于網(wǎng)絡(luò)超時(shí)、套接字關(guān)閉或者任何類型的 I/O 錯(cuò)誤造成的。在非阻塞套接字通信中,該方法能夠處理的唯一錯(cuò)誤是網(wǎng)絡(luò)超時(shí)。為了檢測使用非阻塞通信的網(wǎng)絡(luò)超時(shí),需要編寫稍微多一點(diǎn)的代碼,以確定自從上一次收到數(shù)據(jù)以來已經(jīng)多長時(shí)間了。
哪種方式更好取決于應(yīng)用程序。如果使用的是同步通信,如果數(shù)據(jù)不必在讀取任何數(shù)據(jù)之前處理的話,阻塞通信更好一些,而非阻塞通信則提供了處理任何已經(jīng)讀取的數(shù)據(jù)的機(jī)會(huì)。而異步通信,如 IRC 和聊天客戶機(jī)則要求非阻塞通信以避免凍結(jié)套接字。
![]() ![]() |
![]()
|
創(chuàng)建傳統(tǒng)的非阻塞客戶機(jī)套接字
Java NIO 庫使用通道而非流。通道可同時(shí)用于阻塞和非阻塞通信,但創(chuàng)建時(shí)默認(rèn)為非阻塞版本。但是所有的非阻塞通信都要通過一個(gè)名字中包含 Channel
的類完成。在套接字通信中使用的類是 SocketChannel,
而創(chuàng)建該類的對象的過程不同于典型的套接字所用的過程,如清單 1 所示。
清單 1. 創(chuàng)建并連接 SocketChannel 對象
SocketChannel sc = SocketChannel.open(); sc.connect("www.ibm.com",80); sc.finishConnect(); |
必須聲明一個(gè) SocketChannel
類型的指針,但是不能使用 new
操作符創(chuàng)建對象。相反,必須調(diào)用 SocketChannel
類的一個(gè)靜態(tài)方法打開通道。打開通道后,可以通過調(diào)用 connect()
方法與它連接。但是當(dāng)該方法返回時(shí),套接字不一定是連接的。為了確保套接字已經(jīng)連接,必須接著調(diào)用 finishConnect()
。
當(dāng)套接字連接之后,非阻塞通信就可以開始使用 SocketChannel
類的 read()
和 write()
方法了。也可以把該對象強(qiáng)制轉(zhuǎn)換成單獨(dú)的 ReadableByteChannel
和 WritableByteChannel
對象。無論哪種方式,都要對數(shù)據(jù)使用 Buffer
對象。因?yàn)?NIO 庫的使用超出了本文的范圍,我們不再對此進(jìn)一步討論。
當(dāng)不再需要套接字時(shí),可以使用 close()
方法將其關(guān)閉:
sc.close(); |
這樣就會(huì)同時(shí)關(guān)閉套接字連接和底層的通信通道。
![]() ![]() |
![]()
|
上述方法比傳統(tǒng)的創(chuàng)建套接字連接的例程稍微麻煩一點(diǎn)。不過,傳統(tǒng)的例程也能用于創(chuàng)建非阻塞套接字,不過需要增加幾個(gè)步驟以支持非阻塞通信。
SocketChannel
對象中的底層通信包括兩個(gè) Channel
類: ReadableByteChannel
和 WritableByteChannel。
這兩個(gè)類可以分別從現(xiàn)有的 InputStream
和 OutputStream
阻塞流中使用
Channels
類的 newChannel()
方法創(chuàng)建,如清單 2 所示:
清單 2. 從流中派生通道
ReadableByteChannel rbc = Channels.newChannel(s.getInputStream()); WriteableByteChannel wbc = Channels.newChannel(s.getOutputStream()); |
Channels
類也用于把通道轉(zhuǎn)換成流或者 reader 和 writer。這似乎是把通信切換到阻塞模式,但并非如此。如果試圖讀取從通道派生的流,讀方法將拋出 IllegalBlockingModeException
異常。
相反方向的轉(zhuǎn)換也是如此。不能使用 Channels
類把流轉(zhuǎn)換成通道而指望進(jìn)行非阻塞通信。如果試圖讀從流派生的通道,讀仍然是阻塞的。但是像編程中的許多事情一樣,這一規(guī)則也有例外。
這種例外適合于實(shí)現(xiàn) SelectableChannel
抽象類的類。 SelectableChannel
和它的派生類能夠選擇使用阻塞或者非阻塞模式。 SocketChannel
就是這樣的一個(gè)派生類。
但是,為了能夠在兩者之間來回切換,接口必須作為 SelectableChannel
實(shí)現(xiàn)。對于套接字而言,為了實(shí)現(xiàn)這種能力必須使用 SocketChannel
而不是 Socket
。
回顧一下,要?jiǎng)?chuàng)建套接字,首先必須像通常使用 Socket
類那樣創(chuàng)建一個(gè)套接字。套接字連接之后,使用 清單 2中的兩行代碼把流轉(zhuǎn)換成通道。
清單 3. 創(chuàng)建套接字的另一種方法
Socket s = new Socket("www.ibm.com", 80); ReadableByteChannel rbc = Channels.newChannel(s.getInputStream()); WriteableByteChannel wbc = Channels.newChannel(s.getOutputStream()); |
如前所述,這樣并不能實(shí)現(xiàn)非阻塞套接字通信――所有的通信仍然在阻塞模式下。在這種情況下,非阻塞通信必須模擬實(shí)現(xiàn)。模擬層不需要多少代碼。讓我們來看一看。
模擬層在嘗試讀操作之前首先檢查數(shù)據(jù)的可用性。如果數(shù)據(jù)可讀則開始讀。如果沒有數(shù)據(jù)可用,可能是因?yàn)樘捉幼直魂P(guān)閉,則返回表示這種情況的代碼。在清單 4 中要注意仍然使用了 ReadableByteChannel
讀,盡管 InputStream
完全可以執(zhí)行這個(gè)動(dòng)作。為什么這樣做呢?為了造成是 NIO 而不是模擬層執(zhí)行通信的假象。此外,還可以使模擬層與其他通道更容易結(jié)合,比如向文件通道內(nèi)寫入數(shù)據(jù)。
清單 4. 模擬非阻塞的讀操作
/* The checkConnection method returns the character read when determining if a connection is open. */ y = checkConnection(); if(y <= 0) return y; buffer.putChar((char ) y); return rbc.read(buffer); |
對于非阻塞通信,寫操作只寫入能夠?qū)懙臄?shù)據(jù)。發(fā)送緩沖區(qū)的大小和一次可以寫入的數(shù)據(jù)多少有很大關(guān)系。緩沖區(qū)的大小可以通過調(diào)用 Socket
對象的 getSendBufferSize()
方法確定。在嘗試非阻塞寫操作時(shí)必須考慮到這個(gè)大小。如果嘗試寫入比緩沖塊更大的數(shù)據(jù),必須拆開放到多個(gè)非阻塞寫操作中。太大的單個(gè)寫操作可能被阻塞。
清單 5. 模擬非阻塞的寫操作
int x, y = s.getSendBufferSize(), z = 0; int expectedWrite; byte [] p = buffer.array(); ByteBuffer buf = ByteBuffer.allocateDirect(y); /* If there isn't any data to write, return, otherwise flush the stream */ if(buffer.remaining() == 0) return 0; os.flush() for(x = 0; x < p.length; x += y) { if(p.length - x < y) { buf.put(p, x, p.length - x); expectedWrite = p.length - x; } else { buf.put(p, x, y); expectedWrite = y; } /* Check the status of the socket to make sure it's still open */ if(!s.isConnected()) break; /* Write the data to the stream, flushing immediately afterward */ buf.flip(); z = wbc.write(buf); os.flush(); if(z < expectedWrite) break; buf.clear(); } if(x > p.length) return p.length; else if(x == 0) return -1; else return x + z; |
與讀操作類似,首先要檢查套接字是否仍然連接。但是如果把數(shù)據(jù)寫入 WritableByteBuffer
對象,就像清單 5 那樣,該對象將自動(dòng)進(jìn)行檢查并在沒有連接時(shí)拋出必要的異常。在這個(gè)動(dòng)作之后開始寫數(shù)據(jù)之前,流必須立即被清空,以保證發(fā)送緩沖區(qū)中有發(fā)送數(shù)據(jù)的空間。任何寫操作都要這樣做。發(fā)送到塊中的數(shù)據(jù)與發(fā)送緩沖區(qū)的大小相同。執(zhí)行清除操作可以保證發(fā)送緩沖不會(huì)溢出而導(dǎo)致寫操作被阻塞。
因?yàn)榧俣▽懖僮髦荒軐懭肽軌驅(qū)懙膬?nèi)容,這個(gè)過程還必須檢查套接字保證它在每個(gè)數(shù)據(jù)塊寫入后仍然是打開的。如果在寫入數(shù)據(jù)時(shí)套接字被關(guān)閉,則必須中止寫操作并返回套接字關(guān)閉之前能夠發(fā)送的數(shù)據(jù)量。
BufferedOutputReader
可用于模擬非阻塞寫操作。如果試圖寫入超過緩沖區(qū)兩倍長度的數(shù)據(jù),則直接寫入緩沖區(qū)整倍數(shù)長度的數(shù)據(jù)(緩沖余下的數(shù)據(jù))。比如說,如果緩沖區(qū)的長度是 256 字節(jié)而需要寫入 529 字節(jié)的數(shù)據(jù),則該對象將清除當(dāng)前緩沖區(qū)、發(fā)送 512 字節(jié)然后保存剩下的 17 字節(jié)。
對于非阻塞寫而言,這并非我們所期望的。我們希望分次把數(shù)據(jù)寫入同樣大小的緩沖區(qū)中,并最終把全部數(shù)據(jù)都寫完。如果發(fā)送的大塊數(shù)據(jù)留下一些數(shù)據(jù)被緩沖,那么在所有數(shù)據(jù)被發(fā)送的時(shí)候,寫操作就會(huì)被阻塞。
整個(gè)模擬層可以放到一個(gè)類中,以便更容易和應(yīng)用程序集成。如果要這樣做,我建議從 ByteChannel
派生這個(gè)類。這個(gè)類可以強(qiáng)制轉(zhuǎn)換成單獨(dú)的 ReadableByteChannel
和 WritableByteChannel
類。
清單 6 給出了從 ByteChannel
派生的模擬層類模板的一個(gè)例子。本文后面將一直使用這個(gè)類表示通過阻塞連接執(zhí)行的非阻塞操作。
清單 6. 模擬層的類模板
public class nbChannel implements ByteChannel { Socket s; InputStream is; OutputStream os; ReadableByteChannel rbc; WritableByteChannel wbc; public nbChannel(Socket socket); public int read(ByteBuffer dest); public int write(ByteBuffer src); public void close(); protected int checkConnection(); } |
使用新建的模擬層創(chuàng)建套接字非常簡單。只要像通常那樣創(chuàng)建 Socket
對象,然后創(chuàng)建 nbChannel
對象就可以了,如清單 7 所示:
清單 7. 使用模擬層
Socket s = new Socket("www.ibm.com", 80); nbChannel socketChannel = new nbChannel(s); ReadableByteChannel rbc = (ReadableByteChannel)socketChannel; WritableByteChannel wbc = (WritableByteChannel)socketChannel; |
![]() ![]() |
![]()
|
創(chuàng)建傳統(tǒng)的非阻塞服務(wù)器套接字
服務(wù)器端的非阻塞套接字和客戶端上的沒有很大差別。稍微麻煩一點(diǎn)的只是建立接受輸入連接的套接字。套接字必須通過從服務(wù)器套接字通道派生一個(gè)阻塞的服務(wù)器套接字綁定到阻塞模式。清單 8 列出了需要做的步驟。
清單 8. 創(chuàng)建非阻塞的服務(wù)器套接字(SocketChannel)
ServerSocketChannel ssc = ServerSocketChannel.open(); ServerSocket ss = ssc.socket(); ss.bind(new InetSocketAddress(port)); SocketChannel sc = ssc.accept(); |
與客戶機(jī)套接字通道相似,服務(wù)器套接字通道也必須打開而不是使用 new
操作符或者構(gòu)造函數(shù)。在打開之后,必須派生服務(wù)器套接字對象以便把套接字通道綁定到一個(gè)端口。一旦套接字被綁定,服務(wù)器套接字對象就可以丟棄了。
通道使用 accept()
方法接收到來的連接并把它們轉(zhuǎn)給套接字通道。一旦接收了到來的連接并轉(zhuǎn)給套接字通道對象,通信就可以通過 read()
和 write()
方法開始進(jìn)行了。
![]() ![]() |
![]()
|
實(shí)際上,并非真正的替代。因?yàn)榉?wù)器套接字通道必須使用服務(wù)器套接字對象綁定,為何不完全繞開服務(wù)器套接字通道而僅使用服務(wù)器套接字對象呢?不過這里的通信不使用 SocketChannel
,而要使用模擬層 nbChannel。
清單 9. 建立服務(wù)器套接字的另一種方法
ServerSocket ss = new ServerSocket(port); Socket s = ss.accept(); nbChannel socketChannel = new nbChannel(s); ReadableByteChannel rbc = (ReadableByteChannel)socketChannel; WritableByteChannel wbc = (WritableByteChannel)socketChannel; |
![]() ![]() |
![]()
|
創(chuàng)建SSL連接,我們要分別從客戶端和服務(wù)器端考察。
創(chuàng)建 SS L連接的傳統(tǒng)方法涉及到使用套接字工廠和其他一些東西。我將不會(huì)詳細(xì)討論如何創(chuàng)建SSL連接,不過有一本很好的教程,“Secure your sockets with JSSE”(請參閱 參考資料),從中您可以了解到更多的信息。
創(chuàng)建 SSL 套接字的默認(rèn)方法非常簡單,只包括幾個(gè)很短的步驟:
- 創(chuàng)建套接字工廠。
- 創(chuàng)建連接的套接字。
- 開始握手。
- 派生流。
- 通信。
清單 10 說明了這些步驟:
清單 10. 創(chuàng)建安全的客戶機(jī)套接字
SSLSocketFactory sslFactory = (SSLSocketFactory)SSLSocketFactory.getDefault(); SSLSocket ssl = (SSLSocket)sslFactory.createSocket(host, port); ssl.startHandshake(); InputStream is = ssl.getInputStream(); OutputStream os = ssl.getOutputStream(); |
默認(rèn)方法不包括客戶驗(yàn)證、用戶證書和其他特定連接可能需要的東西。
建立SSL服務(wù)器連接的傳統(tǒng)方法稍微麻煩一點(diǎn),需要加上一些類型轉(zhuǎn)換。因?yàn)檫@些超出了本文的范圍,我將不再進(jìn)一步介紹,而是說說支持SSL服務(wù)器連接的默認(rèn)方法。
創(chuàng)建默認(rèn)的 SSL 服務(wù)器套接字也包括幾個(gè)很短的步驟:
- 創(chuàng)建服務(wù)器套接字工廠。
- 創(chuàng)建并綁定服務(wù)器套接字。
- 接受傳入的連接。
- 開始握手。
- 派生流。
- 通信。
盡管看起來似乎與客戶端的步驟相似,要注意這里去掉了很多安全選項(xiàng),比如客戶驗(yàn)證。
清單 11 說明這些步驟:
清單 11. 創(chuàng)建安全的服務(wù)器套接字
SSLServerSocketFactory sslssf = (SSLServerSocketFactory)SSLServerSocketFactory.getDefault(); SSLServerSocket sslss = (SSLServerSocket)sslssf.createServerSocket(port); SSLSocket ssls = (SSLSocket)sslss.accept(); ssls.startHandshake(); InputStream is = ssls.getInputStream(); OutputStream os = ssls.getOutputStream(); |
![]() ![]() |
![]()
|
要精心實(shí)現(xiàn)安全的非阻塞連接,也需要分別從客戶端和服務(wù)器端來看。
在客戶端建立安全的非阻塞連接非常簡單:
- 創(chuàng)建并連接
Socket
對象。 - 把
Socket
對象添加到模擬層上。 - 通過模擬層通信。
清單 12 說明了這些步驟:
清單 12. 創(chuàng)建安全的客戶機(jī)連接
/* Create the factory, then the secure socket */ SSLSocketFactory sslFactory = (SSLSocketFactory)SSLSocketFactory.getDefault(); SSLSocket ssl = (SSLSocket)sslFactory.createSocket(host, port); /* Start the handshake. Should be done before deriving channels */ ssl.startHandshake(); /* Put it into the emulation layer and create separate channels */ nbChannel socketChannel = new nbChannel(ssl); ReadableByteChannel rbc = (ReadableByteChannel)socketChannel; WritableByteChannel wbc = (WritableByteChannel)socketChannel; |
利用前面給出的 模擬層類 就可以實(shí)現(xiàn)非阻塞的安全連接。因?yàn)榘踩捉幼滞ǖ啦荒苁褂?SocketChannel
類打開,而 Java API 中又沒有完成這項(xiàng)工作的類,所以創(chuàng)建了一個(gè)模擬類。模擬類可以實(shí)現(xiàn)非阻塞通信,無論使用安全套接字連接還是非安全套接字連接。
列出的步驟包括默認(rèn)的安全設(shè)置。對于更高級的安全性,比如用戶證書和客戶驗(yàn)證, 參考資料 部分提供了說明如何實(shí)現(xiàn)的文章。
在服務(wù)器端建立套接字需要對默認(rèn)安全稍加設(shè)置。但是一旦套接字被接收和路由,設(shè)置必須與客戶端的設(shè)置完全相同,如清單 13 所示:
清單 13. 創(chuàng)建安全的非阻塞服務(wù)器套接字
/* Create the factory, then the socket, and put it into listening mode */ SSLServerSocketFactory sslssf = (SSLServerSocketFactory)SSLServerSocketFactory.getDefault(); SSLServerSocket sslss = (SSLServerSocket)sslssf.createServerSocket(port); SSLSocket ssls = (SSLSocket)sslss.accept(); /* Start the handshake on the new socket */ ssls.startHandshake(); /* Put it into the emulation layer and create separate channels */ nbChannel socketChannel = new nbChannel(ssls); ReadableByteChannel rbc = (ReadableByteChannel)socketChannel; WritableByteChannel wbc = (WritableByteChannel)socketChannel; |
同樣,要記住這些步驟使用的是默認(rèn)安全設(shè)置。
![]() ![]() |
![]()
|
多數(shù) Internet 客戶機(jī)應(yīng)用程序,無論使用 Java 語言還是其他語言編寫,都需要提供安全和非安全連接。Java Secure Socket Extensions 庫使得這項(xiàng)工作非常容易,我最近在編寫一個(gè) HTTP 客戶庫時(shí)就使用了這種方法。
SSLSocket
類派生自 Socket。
您可能已經(jīng)猜到我要怎么做了。所需要的只是該對象的一個(gè) Socket
指針。如果套接字連接不使用SSL,則可以像通常那樣創(chuàng)建套接字。如果要使用 SSL,就稍微麻煩一點(diǎn),但此后的代碼就很簡單了。清單 14 給出了一個(gè)例子:
清單 14. 集成安全的和非安全的客戶機(jī)連接
Socket s; ReadableByteChannel rbc; WritableByteChannel wbc; nbChannel socketChannel; if(!useSSL) s = new Socket(host, port); else { SSLSocketFactory sslsf = SSLSocketFactory.getDefault(); SSLSocket ssls = (SSLSocket)SSLSocketFactory.createSocket(host, port); ssls.startHandshake(); s = ssls; } socketChannel = new nbChannel(s); rbc = (ReadableByteChannel)socketChannel; wbc = (WritableByteChannel)socketChannel; ... s.close(); |
創(chuàng)建通道之后,如果套接字使用了SSL,那么就是安全通信,否則就是普通通信。如果使用了 SSL,關(guān)閉套接字將導(dǎo)致握手中止。
這種設(shè)置的一種可能是使用兩個(gè)單獨(dú)的類。一個(gè)類負(fù)責(zé)處理通過套接字沿著與非安全套接字的連接進(jìn)行的所有通信。一個(gè)單獨(dú)的類應(yīng)該負(fù)責(zé)創(chuàng)建安全的連接,包括安全連接的所有必要設(shè)置,無論是否是默認(rèn)的。安全類應(yīng)該直接插入通信類,只有在使用安全連接時(shí)被調(diào)用。
使用J2SEAPI讀取Properties文件的六種方法
1。使用java.util.Properties類的load()方法示例:InputStreamin=lnewBufferedInputStream(newFileInputStream(name));Propertiesp=newProperties();p.load(in);
2。使用java.util.ResourceBundle類的getBundle()方法示例:ResourceBundlerb=ResourceBundle.getBundle(name,Locale.getDefault());
3。使用java.util.PropertyResourceBundle類的構(gòu)造函數(shù)示例:InputStreamin=newBufferedInputStream(newFileInputStream(name));ResourceBundlerb=newPropertyResourceBundle(in);
4。使用class變量的getResourceAsStream()方法示例:InputStreamin=JProperties.class.getResourceAsStream(name);Propertiesp=newProperties();p.load(in);
5。使用class.getClassLoader()所得到的java.lang.ClassLoader的getResourceAsStream()方法示例:InputStreamin=JProperties.class.getClassLoader().getResourceAsStream(name);Propertiesp=newProperties();p.load(in);
6。使用java.lang.ClassLoader類的getSystemResourceAsStream()靜態(tài)方法示例:InputStreamin=ClassLoader.getSystemResourceAsStream(name);Propertiesp=newProperties();p.load(in);
補(bǔ)充
Servlet中可以使用javax.servlet.ServletContext的getResourceAsStream()方法示例:InputStreamin=context.getResourceAsStream(path);Propertiesp=newProperties();p.load(in);
Windows和瀏覽器快捷鍵
使用計(jì)算機(jī)和軟件的最大理由是可以提高工作效率。提高效率的關(guān)鍵,一是佳軟,二是善用。熟練掌握熱鍵,乃是高效工作之道的基礎(chǔ)和不二法門。下文針對最經(jīng)典的軟件,列舉了最實(shí)用的快捷鍵,涉及:操作系統(tǒng)、瀏覽器、播放器、交流工具、文件管理工具、文本編輯等。要注意的是,相同的全局熱鍵,只能讓一個(gè)程序生效。
Windows 及相應(yīng)軟件
1 最小化所有窗口(顯示桌面)/恢復(fù)原狀 Win + D
2 打開運(yùn)行對話框 Win + R
3 打開系統(tǒng)屬性 Win + Break/Pause
4 復(fù)制某一對象 按住CTRL拖動(dòng)
5 選中/高亮文本塊 CTRL+SHIFT+方向鍵
6 按打開的順序在窗口間切換 Alt + Esc
7 復(fù)制文件 CTRL + C
8 粘貼文件 CTRL + V
9 剪切文件 CTRL + X
10 還原 CTRL + Z
11 撤消還原操作(如果可能的話) CTRL + Y
12 打開輔助工具 Win + U
13 打開資源管理器 Win + E
14 打開上下文菜單 Shift + F10
15 多頁簽時(shí),在頁簽間切換 Ctrl + Tab
更多Windows全局熱鍵: Windows keyboard shortcuts
更多Mac OS全局熱鍵: Mac OS X keyboard shortcuts
更多Linux全局熱鍵: Linux Keyboard Shortcuts You Should Know About
瀏覽器: Firefox
16 去除csS樣式 Alt + V + Y + N (或 CTRL + Shift + S + Web Developer’s 工具欄)
17 恢復(fù)CSS樣式 Alt + V + Y + B
18 查看源代碼 Ctrl + U
19 查看選中內(nèi)容的源代碼 選取內(nèi)容,Shift + F10,點(diǎn)“Show source code”
20 智能 DOM 偵測 Ctrl + Shift + I
21 啟動(dòng)Firebug F12
22 添加書簽 Ctrl + D
23 書簽 Ctrl + B
24 歷鳴 Ctrl + H
25 找開剛關(guān)閉的標(biāo)簽 CTRL+SHIFT+T
26 全部標(biāo)簽加入書簽 CTRL+SHIFT+D
27 返回 Alt + Left Arrow
28 前進(jìn) Alt + Right Arrow
29 訪問歷史中后退一步 Backspace
30 為書簽添加關(guān)鍵詞 這樣做可以更快的訪問書簽。右擊書簽,選擇屬性,輸入合適的關(guān)鍵詞。保存后,你只要在地址欄輸入關(guān)鍵詞并回車,就可以訪問書簽了。
31 跳轉(zhuǎn)到地址欄 Ctrl + L 或 F6
32 回到主頁 Alt + Home
33 減小文本字號(hào) Ctrl + -
34 增大文本字號(hào) Ctrl + +
35 回到主頁(與32重復(fù)) Alt + Home
36 快速搜索 /
37 跳轉(zhuǎn)到搜索條 Ctrl + K
38 在標(biāo)簽歷史上跳轉(zhuǎn) ALT + ← (后退), ALT + → (前進(jìn))
39 新建標(biāo)簽 Ctrl + T (鍵盤), 雙擊標(biāo)簽條 (鼠標(biāo))
40 關(guān)閉當(dāng)前書簽 Ctrl + W (鍵盤), 中鍵標(biāo)簽 (鼠標(biāo))
41 到下一標(biāo)簽 Ctrl + Page up 或 CTRL + Tab
42 到前一標(biāo)簽 Ctrl + Page Dn 或 Ctrl + Shift + Tab
43 新標(biāo)簽中打開鏈接 Ctrl + 左擊
43 選擇標(biāo)簽 Ctrl + [1 - 9]
45 到下一鏈接 Tab
46 到前一鏈接 Shift + Tab
47 輸入框中顯示輸入過的文字,或下拉菜單中顯示可選項(xiàng) Alt + ↓
瀏覽器: Internet Explorer 7
48 在新的后臺(tái)標(biāo)簽打開鏈接 CTRL+鼠標(biāo)左鍵或中鍵
49 在新的前臺(tái)標(biāo)簽打開鏈接 CTRL+SHIFT+鼠標(biāo)左鍵或中鍵
50 打開快速標(biāo)簽視圖 CTRL+Q
51 顯示已打開標(biāo)簽列表 CTRL+SHIFT+Q
52 轉(zhuǎn)到地址欄 Alt + D
53 在新標(biāo)簽中打開輸入的地址 Address Bar in new tab Alt + Enter
54 轉(zhuǎn)到搜索條 Ctrl + E
55 新建標(biāo)簽 Ctrl + T (keyboard), Double Click on Tab Bar (mouse)
56 關(guān)閉當(dāng)前標(biāo)簽 Ctrl + W (keyboard), Middle Click on Tab (mouse)
57 到下一標(biāo)簽 Ctrl + Tab
58 到前一標(biāo)簽 Ctrl + Shift + Tab
59 到某一標(biāo)簽 Ctrl + [1 - 9]
60 打開feeds CTRL+J
61 到下一標(biāo)簽 Tab
62 到前一標(biāo)簽 Shift + Tab
Safari 熱鍵
更多 MSIE 7熱鍵
播放器快捷鍵
音樂播放: Winamp
要使用全局熱鍵,須設(shè)置:Main Windows -> Options > Preferences > Global Hotkeys。其熱鍵可自定義。
63 增大音量 CTRL + ALT + ↑
64 減小音量 CTRL + ALT + ↓
65 播放、重新開始或恢復(fù)播放 Winamp 窗口: x
66 暫停 CTRL + ALT + Home (Winamp 窗口: c)
67 播放 CTRL + ALT + Insert (Winamp 窗口: x)
68 停止 CTRL + ALT + End (Winamp 窗口: v)
69 前一首 CTRL + ALT + PgUp (Winamp 窗口: z)
70 下一首 CTRL + ALT + PgDn (Winamp 窗口: b)
71 返回5秒 CTRL + ALT + ← (Winamp 窗口: ←)
72 快進(jìn)5秒 CTRL + ALT + → (Winamp 窗口: →)
73 切換重復(fù) r
74 切換隨機(jī) s
75 添加文件 l
76 添加目錄 Shift + l
77 隨機(jī)播放列表 CTRL + Shift + r
更多熱鍵: Winamp Shortcuts
Winamp Global Hotkeys
iTunes 熱鍵: Hotkeys for iTunes
Last.FM 熱鍵: Autohotkey Script for Last.FM
通訊: Thunderbird
在Skype中可自定義熱鍵: Main Window -> Tools -> Options -> Hotkeys.
在Thunderbird中使用熱鍵: Thunderbird Help: Keyboard Shortcuts 和 All hotkeys: Mozilla Thunderbird Hotkeys
xbeta提醒:Thunderbird和Firefox很多熱鍵是相通的,并且可用keyconfig擴(kuò)展更改和自定義更多熱鍵。
78 轉(zhuǎn)到下一封信 F
79 轉(zhuǎn)到下一封未讀信件 N
80 轉(zhuǎn)到上一封未讀信件 B
81 增大字號(hào) Ctrl + +
82 減小字號(hào) CTRL + -
83 標(biāo)記信件為已讀/未讀 M
84 標(biāo)記為垃圾郵件 J
85 標(biāo)記為非垃圾郵件 SHIFT + J
86 查看信件源文件 CTRL + U
87 新建信件 CTRL + M, CTRL + N, Cmd + Shift + M (Mac)
88 回復(fù)信件 Ctrl + R
89 收取當(dāng)前賬戶的信件 CTRL+T
90 收取所有賬戶的信件 CTRL+SHIFT+T
91 打開已收的信件 CTRL + O
92 發(fā)送信件 CTRL + Enter/Return
通訊: Google Mail
93 寫郵件 c, + c 在新窗口中寫信
94 回信 r, + r 在新窗口中回信
95 轉(zhuǎn)發(fā)信件 f, + f 在新窗口中轉(zhuǎn)發(fā)信件
96 轉(zhuǎn)到收件箱 g 然后按 i
97 定位到搜索框 /
98 轉(zhuǎn)到下一封信 n
99 轉(zhuǎn)到上一封信 p
100 報(bào)告垃圾郵件 !
Total Commander快捷鍵
文件管理: Total CoMMander
xbeta注:Total Commander是Windows 下鍵盤操作的典范和極致。用TC不會(huì)熱鍵,等于不懂TC。喜歡熱鍵操作而未用TC——xbeta想不出會(huì)有這種情況。TC的熱鍵是高度可以自定義的。如下舉例僅是管中窺豹,還有更多熱鍵需要你的發(fā)現(xiàn),比如很重要的 alt+F5, alt+F9, alt+enter, →,space, alt+shift+enter, backspace,+, alt+, alt+F7, 及中國用戶廣泛自定義的ctrl+1,ctrl+2, ctrl+3等。
101 激活或取消激活左側(cè)菜單 F10
102 比較和同步文件夾 SHIFT+F2
103 新建文本文件,并用編輯器打開 SHIFT + F4
104 在同一目錄復(fù)制文件并改名 SHIFT + F5
105 重命名文件 SHIFT + F6
106 縮小到系統(tǒng)托盤圖標(biāo) SHIFT + Esc
107 轉(zhuǎn)到剛訪問的前/后一個(gè)目錄 ALT+left/right
108 顯示已訪問目錄歷史清單 ALT + Arrow Down
109 全選 CTRL+NUM +, CTRL + A
110 全不選 CTRL+NUM -
111 返回上級目錄 (cd ..) CTRL+PgUp or Backspace
112 返回根目錄 (多數(shù)歐洲鍵盤)(xbeta:適用于中國用戶) CTRL+
113 返回根目錄 (美國鍵盤布局)(xbeta注:對中國用戶是新建目錄) F7
114 按文件名排序 CTRL+F3
115 按后綴排序 CTRL+F4
116 按時(shí)間/日期排序 CTRL+F5
117 按大小排序 CTRL+F6
118 顯示所有文件 CTRL+F10
119 僅顯示可執(zhí)行文件 CTRL + F11
120 顯示用戶自定義的文件 CTRL + F12
121 顯示文件屬性 ALT+ENTER
122 并列顯示目錄及所有子目錄下的全部文件(xbeta:這是一個(gè)非常實(shí)用的功能,不用TC的人體會(huì)不到) CTRL + B
123 進(jìn)入目錄收藏列表(書簽)(xbeta:TC用戶就稱之為ctrl+d) CTRL + D
124 連接FTP CTRL + F (斷開: CTRL + SHIFT + F)
125 批量改名(xbeta:極其強(qiáng)大) CTRL + M
126 進(jìn)入命令行對話框并帶入當(dāng)前路徑 CTRL + P
127 快速瀏覽(xbeta:這是TC最常用、最實(shí)用的熱鍵。相似功能F3) CTRL + Q
128 新建目錄標(biāo)簽并激活它 CTRL + T (CTRL + SHIFT + T是新建標(biāo)簽但不激活)
129 左右窗口交換目錄 CTRL + U
130 左右窗口交換目錄和標(biāo)簽 CTRL + SHIFT + U
131 把光標(biāo)處目錄在新標(biāo)簽打開 CTRL + Arrow Up
132 轉(zhuǎn)到下一標(biāo)簽 CTRL + TAB
133 轉(zhuǎn)到上一標(biāo)簽 CTRL+SHIFT+TAB
134 定位到某一目錄/文件(xbeta強(qiáng)烈建議定位框模式) CTRL+ALT+字母(s)
Wordpress
135 加粗 Alt + Shift + B
136 斜體 Alt + Shift + I
137 引用 Alt + Shift + Q
138 列表 (ul) Alt + Shift + U
139 序號(hào)列表 (ol) Alt + Shift + O
140 列表項(xiàng) (li) Alt + Shift + L
141 代碼 Alt + Shift + C
142 插入 Alt + Shift + S
143 刪除 Alt + Shift + D
144 鏈接 Alt + Shift + A
145 more (Read More tag) Alt + Shift + T
146 發(fā)表文章 Alt + Shift + P
通訊: microsoft Office Outlook
147 切換到郵件 CTRL + 1
148 切換到日歷 CTRL + 2
149 切換到通訊錄 Ctrl + 3
150 切換到任務(wù) Ctrl + 4
151 切換到筆記 Ctrl + 5
152 新建約會(huì) CTRL+SHIFT+A
153 新建聯(lián)系人 CTRL+SHIFT+C
154 新建日志項(xiàng) CTRL+SHIFT+J
155 新建會(huì)議 CTRL+SHIFT+Q
156 新建信件 CTRL+SHIFT+M
157 新建筆記 CTRL+SHIFT+N
158 新建任務(wù) CTRL + SHIFT + K
159 拼寫檢查 F7
160 轉(zhuǎn)發(fā)信件 CTRL + F
161 搜索 F4
162 切換到收件箱 CTRL+SHIFT+I
163 切換到發(fā)件箱 CTRL+SHIFT+O
164 發(fā)送 Alt + S
165 回信 Ctrl + SHIFT + R
166 收信 CTRL+M or F9
167 寫信 CTRL + N
168 打開信件 CTRL + O
169 標(biāo)為已讀 CTRL + Q
Outlook的全局熱鍵: Keyboard shortcuts for Outlook
ICQ、ACDSee
通訊: ICQ
170 模仿在系統(tǒng)托盤中雙擊 CTRL + SHIFT + I
171 激活/取消激活用戶窗口 CTRL + SHIFT + A
172 關(guān)閉 ICQ Control + Shift + I and then Alt + F4
173 發(fā)送url給聯(lián)系人 Control + Shift + F6
174 改變狀態(tài) Alt + S +
175 接收信息 Control + Shift + I
176 選擇好友 Insert +
圖像管理: ACDSee Viewer
177 縮小顯示比例(xbeta:推薦用免費(fèi)軟件IrfanView或XnView代替ACDSee) - (就是減號(hào))
178 增加顯示比例 +
179 復(fù)制當(dāng)前項(xiàng)到某目錄 ALT + C
180 移動(dòng)當(dāng)前項(xiàng)到某目錄 ALT + M
181 改名當(dāng)前項(xiàng)到某目錄 ALT + R
182 打開屬性面板 Alt + Enter
183 顯/隱狀態(tài)欄 b
184 顯/隱菜單欄 Ctrl + Shift + M
185 用默認(rèn)程序打開當(dāng)前圖像 CTRL + E
186 進(jìn)入轉(zhuǎn)換對話框 CTRL + F
187 復(fù)制選中部分到剪貼板 Ctrl + Insert
188 打開旋轉(zhuǎn)/鏡像對話框(xbeta:還是IrfanView方便) CTRL + J
189 打開當(dāng)前圖像,進(jìn)入編輯模式,激活調(diào)節(jié)曝光工具 Ctrl + L
190 打開當(dāng)前圖像,進(jìn)入編輯模式,激活調(diào)節(jié)大小工具 CTRL+R
191 清空選中內(nèi)容 CTRL+Q
192 保存圖像 CTRL + S
193 關(guān)閉查看窗口 CTRL + W
194 90度順時(shí)針旋轉(zhuǎn)當(dāng)前圖片(xbeta注:IrfanView只要按一下r即可) Ctrl + Alt +
195 改變圖像為黑白兩色 Ctrl + Shift + 1
196 進(jìn)入批量處理菜單(xbeta注:IrfanView按B即可) CTRL + Alt + B
197 以默認(rèn)程序打開當(dāng)前文件 Shift + E
在小巧但極其強(qiáng)大的IrfanView中使用熱鍵: Irfanview
編輯: Ultraedit
說到編輯器的快捷鍵,VIM是無與倫比的。要反對,也得是帶腳踏板的Emacs。UE還是有差距的,很大差距。注意:VIM是開源、免費(fèi)的,而UE則需要注冊。UE是Windows下最好的編輯器——如果沒有GVIM和Emacs的話。而VIM和Emacs則是任何操作系統(tǒng)下最好的編輯器。
198 自動(dòng)換行 CTRL + W
199 插入當(dāng)前日期/時(shí)間 F7
200 找到匹配的括號(hào) (,[,{ or },],) CTRL + B
201 段落重新格式化 CTRL + T
202 Tag 列表 CTRL + F8
203 轉(zhuǎn)換所選文字為小寫 CTRL + F5
204 轉(zhuǎn)換所選文字為大寫 Alt + F5
205 激活拼寫檢查 CTRL + K
206 切換列/塊模式 ALT + C
207 設(shè)定書簽 CTRL + F2
208 轉(zhuǎn)到下一書簽 F2
209 插入用戶定義的模板 Alt+0-9 or Shift+Alt+0-9
210 上滾一行,光標(biāo)不變 CTRL + Up
211 下滾一行,光標(biāo)不變 CTRL + Down
212 顯示函數(shù)列表 F8
213 到下一段 Alt + Right
214 到上一段 Alt + Left
雪絨花(Edelweiss),又叫高山火絨草(德語中意為:高貴的白色),只生長在阿爾卑斯山脈的林線以上。雪絨花約有40個(gè)種類,分布在亞洲和歐洲的阿爾卑斯山脈一帶。這種花在阿爾卑斯山脈中通常生長在海拔1700米以上的地方,由于它只生長在非常少有的巖石地表上,因而極為稀少。
在奧地利,雪絨花象征著勇敢,因?yàn)橐吧难┙q花生長在條件艱苦的山上,常人難以得見其美麗容顏,所以見過雪絨花的人都是英雄。從前,奧地利許多年輕人冒著生命危險(xiǎn),攀上陡峭的山崖,只為摘下一朵雪絨花獻(xiàn)給自己的心上人,只有雪絨花才能代表為愛犧牲一切的決心。關(guān)于雪絨花,奧地利有許多傳說。人們相信雪絨花王可以指引那些采摘者們找到他們尋覓已久的花,但如果誰將花兒連根拔去,這個(gè)人將會(huì)墜入萬丈深淵。
浪漫的傳說固然令人神往,當(dāng)雪絨花與軍人聯(lián)系起來的時(shí)候,它才被賦予新的意義,當(dāng)佩帶雪絨花的第三帝國軍人憑借非凡的勇氣征服一座座雪山的時(shí)候,渺小者的力量得到了充分的體現(xiàn),德軍用它作為自己山地部隊(duì)的標(biāo)志。傳說每個(gè)志在加入山地部隊(duì)的年輕人,都必須只攜帶最少的裝備爬上阿爾卑斯山,采下一朵雪絨花,以證明自己適合做一個(gè)真正的山地軍人。
