2009年7月15日
#
APACHE是一個(gè)web服務(wù)器環(huán)境程序 啟用他可以作為web服務(wù)器使用 不過(guò)只支持靜態(tài)網(wǎng)頁(yè) 如(asp,php,cgi,jsp)等動(dòng)態(tài)網(wǎng)頁(yè)的就不行
如果要在APACHE環(huán)境下運(yùn)行jsp 的話就需要一個(gè)解釋器來(lái)執(zhí)行jsp網(wǎng)頁(yè) 而這個(gè)jsp解釋器就是TOMCAT, 為什么還要JDK呢?因?yàn)閖sp需要連接數(shù)據(jù)庫(kù)的話 就要jdk來(lái)提供連接數(shù)據(jù)庫(kù)的驅(qū)程,所以要運(yùn)行jsp的web服務(wù)器平臺(tái)就需要APACHE+TOMCAT+JDK
整合的好處是:
如果客戶端請(qǐng)求的是靜態(tài)頁(yè)面,則只需要Apache服務(wù)器響應(yīng)請(qǐng)求
如果客戶端請(qǐng)求動(dòng)態(tài)頁(yè)面,則是Tomcat服務(wù)器響應(yīng)請(qǐng)求
因?yàn)閖sp是服務(wù)器端解釋代碼的,這樣整合就可以減少Tomcat的服務(wù)開(kāi)銷(xiāo)
============================幾種常見(jiàn)的服務(wù)器===============================
① Microsoft IIS
Microsoft的Web服務(wù)器產(chǎn)品為Internet Information Server (IIS), IIS 是允許在公共Intranet或Internet上發(fā)布信息的Web服務(wù)器。IIS是目前最流行的Web服務(wù)器產(chǎn)品之一,很多著名的網(wǎng)站都是建立在IIS的平臺(tái)上。IIS提供了一個(gè)圖形界面的管理工具,稱(chēng)為 Internet服務(wù)管理器,可用于監(jiān)視配置和控制Internet服務(wù)。
IIS是一種Web服務(wù)組件,其中包括Web服務(wù)器、FTP服務(wù)器、NNTP服務(wù)器和SMTP服務(wù)器,分別用于網(wǎng)頁(yè)瀏覽、文件傳輸、新聞服務(wù)和郵件發(fā)送等方面,它使得在網(wǎng)絡(luò)(包括互聯(lián)網(wǎng)和局域網(wǎng))上發(fā)布信息成了一件很容易的事。它提供ISAPI(Intranet Server API)作為擴(kuò)展Web服務(wù)器功能的編程接口;同時(shí),它還提供一個(gè)Internet數(shù)據(jù)庫(kù)連接器,可以實(shí)現(xiàn)對(duì)數(shù)據(jù)庫(kù)的查詢和更新。
② IBM WebSphere
WebSphere Application Server 是一種功能完善、開(kāi)放的Web應(yīng)用程序服務(wù)器,是IBM電子商務(wù)計(jì)劃的核心部分,它是基于 Java 的應(yīng)用環(huán)境,用于建立、部署和管理 Internet 和 Intranet Web 應(yīng)用程序。 這一整套產(chǎn)品進(jìn)行了擴(kuò)展,以適應(yīng) Web 應(yīng)用程序服務(wù)器的需要,范圍從簡(jiǎn)單到高級(jí)直到企業(yè)級(jí)。
WebSphere 針對(duì)以 Web 為中心的開(kāi)發(fā)人員,他們都是在基本 HTTP服務(wù)器和 CGI 編程技術(shù)上成長(zhǎng)起來(lái)的。IBM 將提供 WebSphere 產(chǎn)品系列,通過(guò)提供綜合資源、可重復(fù)使用的組件、功能強(qiáng)大并易于使用的工具、以及支持 HTTP 和 IIOP 通信的可伸縮運(yùn)行時(shí)環(huán)境,來(lái)幫助這些用戶從簡(jiǎn)單的 Web 應(yīng)用程序轉(zhuǎn)移到電子商務(wù)世界。
③ BEA WebLogic Server
是一種多功能、基于標(biāo)準(zhǔn)的web應(yīng)用服務(wù)器,為企業(yè)構(gòu)建自己的應(yīng)用提供了堅(jiān)實(shí)的基礎(chǔ)。各種應(yīng)用開(kāi)發(fā)、部署所有關(guān)鍵性的任務(wù),無(wú)論是集成各種系統(tǒng)和數(shù)據(jù)庫(kù),還是提交服務(wù)、跨 Internet 協(xié)作,起始點(diǎn)都是 BEA WebLogic Server。由于 它具有全面的功能、對(duì)開(kāi)放標(biāo)準(zhǔn)的遵從性、多層架構(gòu)、支持基于組件的開(kāi)發(fā),基于 Internet 的企業(yè)都選擇它來(lái)開(kāi)發(fā)、部署最佳的應(yīng)用。
BEA WebLogic Server 在使應(yīng)用服務(wù)器成為企業(yè)應(yīng)用架構(gòu)的基礎(chǔ)方面繼續(xù)處于領(lǐng)先地位。BEA WebLogic Server 為構(gòu)建集成化的企業(yè)級(jí)應(yīng)用提供了穩(wěn)固的基礎(chǔ),它們以 Internet 的容量和速度,在連網(wǎng)的企業(yè)之間共享信息、提交服務(wù),實(shí)現(xiàn)協(xié)作自動(dòng)化。BEA WebLogic Server 的遵從 J2EE 、面向服務(wù)的架構(gòu),以及豐富的工具集支持,便于實(shí)現(xiàn)業(yè)務(wù)邏輯、數(shù)據(jù)和表達(dá)的分離,提供開(kāi)發(fā)和部署各種業(yè)務(wù)驅(qū)動(dòng)應(yīng)用所必需的底層核心功能。
④ IPlanet Application Server
作為Sun與Netscape聯(lián)盟產(chǎn)物的iPlanet公司生產(chǎn)的iPlanet Application Server 滿足最新J2EE規(guī)范的要求。它是一種完整的WEB服務(wù)器應(yīng)用解決方案,它允許企業(yè)以便捷的方式,開(kāi)發(fā)、部署和管理關(guān)鍵任務(wù) Internet 應(yīng)用。該解決方案集高性能、高度可伸縮和高度可用性于一體,可以支持大量的具有多種客戶機(jī)類(lèi)型與數(shù)據(jù)源的事務(wù)。
iPlanet Application Server的基本核心服務(wù)包括事務(wù)監(jiān)控器、多負(fù)載平衡選項(xiàng)、對(duì)集群和故障轉(zhuǎn)移全面的支持、集成的XML 解析器和可擴(kuò)展格式語(yǔ)言轉(zhuǎn)換(XLST)引擎以及對(duì)國(guó)際化的全面支持。iPlanet Application Server 企業(yè)版所提供的全部特性和功能,并得益于J2EE系統(tǒng)構(gòu)架,擁有更好的商業(yè)工作流程管理工具和應(yīng)用集成功能。
⑤Oracle IAS
Oracle iAS的英文全稱(chēng)是Oracle Internet Application Server,即Internet應(yīng)用服務(wù)器,Oracle iAS是基于Java的應(yīng)用服務(wù)器,通過(guò)與Oracle 數(shù)據(jù)庫(kù)等產(chǎn)品的結(jié)合,Oracle iAS能夠滿足Internet應(yīng)用對(duì)可靠性、可用性和可伸縮性的要求。
Oracle iAS最大的優(yōu)勢(shì)是其集成性和通用性,它是一個(gè)集成的、通用的中間件產(chǎn)品。在集成性方面,Oracle iAS將業(yè)界最流行的HTTP服務(wù)器Apache集成到系統(tǒng)中,集成了Apache的Oracle iAS通信服務(wù)層可以處理多種客戶請(qǐng)求,包括來(lái)自Web瀏覽器、胖客戶端和手持設(shè)備的請(qǐng)求,并且根據(jù)請(qǐng)求的具體內(nèi)容,將它們分發(fā)給不同的應(yīng)用服務(wù)進(jìn)行處理。在通用性方面,Oracle iAS支持各種業(yè)界標(biāo)準(zhǔn),包括 JavaBeans、CORBA、Servlets以及XML標(biāo)準(zhǔn)等,這種對(duì)標(biāo)準(zhǔn)的全面支持使得用戶很容易將在其他系統(tǒng)平臺(tái)上開(kāi)發(fā)的應(yīng)用移植到Oracle平臺(tái)上。
⑥ Apache
Apache源于NCSAhttpd服務(wù)器,經(jīng)過(guò)多次修改,成為世界上最流行的Web服務(wù)器軟件之一。Apache是自由軟件,所以不斷有人來(lái)為它開(kāi)發(fā)新的功能、新的特性、修改原來(lái)的缺陷。Apache的特點(diǎn)是簡(jiǎn)單、速度快、性能穩(wěn)定,并可做代理服務(wù)器來(lái)使用。本來(lái)它只用于小型或試驗(yàn)Internet網(wǎng)絡(luò),后來(lái)逐步擴(kuò)充到各種Unix系統(tǒng)中,尤其對(duì)Linux的支持相當(dāng)完美。
Apache是以進(jìn)程為基礎(chǔ)的結(jié)構(gòu),進(jìn)程要比線程消耗更多的系統(tǒng)開(kāi)支,不太適合于多處理器環(huán)境,因此,在一個(gè)Apache Web站點(diǎn)擴(kuò)容時(shí),通常是增加服務(wù)器或擴(kuò)充群集節(jié)點(diǎn)而不是增加處理器。到目前為止Apache仍然是世界上用的最多的Web服務(wù)器,世界上很多著名的網(wǎng)站都是Apache的產(chǎn)物,它的成功之處主要在于它的源代碼開(kāi)放、有一支開(kāi)放的開(kāi)發(fā)隊(duì)伍、支持跨平臺(tái)的應(yīng)用(可以運(yùn)行在幾乎所有的Unix、Windows、Linux系統(tǒng)平臺(tái)上)以及它的可移植性等方面。
⑦ Tomcat
Tomcat是一個(gè)開(kāi)放源代碼、運(yùn)行servlet和JSP Web應(yīng)用軟件的基于Java的Web應(yīng)用軟件容器。Tomcat Server是根據(jù)servlet和JSP規(guī)范進(jìn)行執(zhí)行的,因此我們就可以說(shuō)Tomcat Server也實(shí)行了Apache-Jakarta規(guī)范且比絕大多數(shù)商業(yè)應(yīng)用軟件服務(wù)器要好。
Tomcat是Java Servlet 2.2和JavaServer Pages 1.1技術(shù)的標(biāo)準(zhǔn)實(shí)現(xiàn),是基于Apache許可證下開(kāi)發(fā)的自由軟件。Tomcat是完全重寫(xiě)的Servlet API 2.2和JSP 1.1兼容的Servlet/JSP容器。Tomcat使用了JServ的一些代碼,特別是Apache服務(wù)適配器。隨著Catalina Servlet引擎的出現(xiàn),Tomcat第四版號(hào)的性能得到提升,使得它成為一個(gè)值得考慮的Servlet/JSP容器,因此目前許多WEB服務(wù)器都是采用Tomcat。
web服務(wù)器和應(yīng)用服務(wù)器得區(qū)別
通俗的講,Web服務(wù)器傳送(serves)頁(yè)面使瀏覽器可以瀏覽,然而應(yīng)用程序服務(wù)器提供的是客戶端應(yīng)用程序可以調(diào)用(call)的方法(methods)。確切一點(diǎn),你可以說(shuō):Web服務(wù)器專(zhuān)門(mén)處理HTTP請(qǐng)求(request),但是應(yīng)用程序服務(wù)器是通過(guò)很多協(xié)議來(lái)為應(yīng)用程序提供(serves)商業(yè)邏輯(business logic)。
下面讓我們來(lái)細(xì)細(xì)道來(lái):
Web服務(wù)器(Web Server)
Web服務(wù)器可以解析(handles)HTTP協(xié)議。當(dāng)Web服務(wù)器接收到一個(gè)HTTP請(qǐng)求(request),會(huì)返回一個(gè)HTTP響應(yīng)(response),例如送回一個(gè)HTML頁(yè)面。為了處理一個(gè)請(qǐng)求(request),Web服務(wù)器可以響應(yīng)(response)一個(gè)靜態(tài)頁(yè)面或圖片,進(jìn)行頁(yè)面跳轉(zhuǎn)(redirect),或者把動(dòng)態(tài)響應(yīng)(dynamic response)的產(chǎn)生委托(delegate)給一些其它的程序例如CGI腳本,JSP(JavaServer Pages)腳本,servlets,ASP(Active Server Pages)腳本,服務(wù)器端(server-side)JavaScript,或者一些其它的服務(wù)器端(server-side)技術(shù)。無(wú)論它們(譯者注:腳本)的目的如何,這些服務(wù)器端(server-side)的程序通常產(chǎn)生一個(gè)HTML的響應(yīng)(response)來(lái)讓瀏覽器可以瀏覽。
要知道,Web服務(wù)器的代理模型(delegation model)非常簡(jiǎn)單。當(dāng)一個(gè)請(qǐng)求(request)被送到Web服務(wù)器里來(lái)時(shí),它只單純的把請(qǐng)求(request)傳遞給可以很好的處理請(qǐng)求(request)的程序(譯者注:服務(wù)器端腳本)。Web服務(wù)器僅僅提供一個(gè)可以執(zhí)行服務(wù)器端(server-side)程序和返回(程序所產(chǎn)生的)響應(yīng)(response)的環(huán)境,而不會(huì)超出職能范圍。服務(wù)器端(server-side)程序通常具有事務(wù)處理(transaction processing),數(shù)據(jù)庫(kù)連接(database connectivity)和消息(messaging)等功能。
雖然Web服務(wù)器不支持事務(wù)處理或數(shù)據(jù)庫(kù)連接池,但它可以配置(employ)各種策略(strategies)來(lái)實(shí)現(xiàn)容錯(cuò)性(fault tolerance)和可擴(kuò)展性(scalability),例如負(fù)載平衡(load balancing),緩沖(caching)。集群特征(clustering—features)經(jīng)常被誤認(rèn)為僅僅是應(yīng)用程序服務(wù)器專(zhuān)有的特征。
應(yīng)用程序服務(wù)器(The Application Server)
根據(jù)我們的定義,作為應(yīng)用程序服務(wù)器,它通過(guò)各種協(xié)議,可以包括HTTP,把商業(yè)邏輯暴露給(expose)客戶端應(yīng)用程序。Web服務(wù)器主要是處理向?yàn)g覽器發(fā)送HTML以供瀏覽,而應(yīng)用程序服務(wù)器提供訪問(wèn)商業(yè)邏輯的途徑以供客戶端應(yīng)用程序使用。應(yīng)用程序使用此商業(yè)邏輯就象你調(diào)用對(duì)象的一個(gè)方法(或過(guò)程語(yǔ)言中的一個(gè)函數(shù))一樣。
應(yīng)用程序服務(wù)器的客戶端(包含有圖形用戶界面(GUI)的)可能會(huì)運(yùn)行在一臺(tái)PC、一個(gè)Web服務(wù)器或者甚至是其它的應(yīng)用程序服務(wù)器上。在應(yīng)用程序服務(wù)器與其客戶端之間來(lái)回穿梭(traveling)的信息不僅僅局限于簡(jiǎn)單的顯示標(biāo)記。相反,這種信息就是程序邏輯(program logic)。 正是由于這種邏輯取得了(takes)數(shù)據(jù)和方法調(diào)用(calls)的形式而不是靜態(tài)HTML,所以客戶端才可以隨心所欲的使用這種被暴露的商業(yè)邏輯。
在大多數(shù)情形下,應(yīng)用程序服務(wù)器是通過(guò)組件(component)的應(yīng)用程序接口(API)把商業(yè)邏輯暴露(expose)(給客戶端應(yīng)用程序)的,例如基于J2EE(Java 2 Platform, Enterprise Edition)應(yīng)用程序服務(wù)器的EJB(Enterprise JavaBean)組件模型。此外,應(yīng)用程序服務(wù)器可以管理自己的資源,例如看大門(mén)的工作(gate-keeping duties)包括安全(security),事務(wù)處理(transaction processing),資源池(resource pooling), 和消息(messaging)。就象Web服務(wù)器一樣,應(yīng)用程序服務(wù)器配置了多種可擴(kuò)展(scalability)和容錯(cuò)(fault tolerance)技術(shù)。
一個(gè)例子
例如,設(shè)想一個(gè)在線商店(網(wǎng)站)提供實(shí)時(shí)定價(jià)(real-time pricing)和有效性(availability)信息。這個(gè)站點(diǎn)(site)很可能會(huì)提供一個(gè)表單(form)讓你來(lái)選擇產(chǎn)品。當(dāng)你提交查詢(query)后,網(wǎng)站會(huì)進(jìn)行查找(lookup)并把結(jié)果內(nèi)嵌在HTML頁(yè)面中返回。網(wǎng)站可以有很多種方式來(lái)實(shí)現(xiàn)這種功能。我要介紹一個(gè)不使用應(yīng)用程序服務(wù)器的情景和一個(gè)使用應(yīng)用程序服務(wù)器的情景。觀察一下這兩中情景的不同會(huì)有助于你了解應(yīng)用程序服務(wù)器的功能。
情景1:不帶應(yīng)用程序服務(wù)器的Web服務(wù)器
在此種情景下,一個(gè)Web服務(wù)器獨(dú)立提供在線商店的功能。Web服務(wù)器獲得你的請(qǐng)求(request),然后發(fā)送給服務(wù)器端(server-side)可以處理請(qǐng)求(request)的程序。此程序從數(shù)據(jù)庫(kù)或文本文件(flat file,譯者注:flat file是指沒(méi)有特殊格式的非二進(jìn)制的文件,如properties和XML文件等)中查找定價(jià)信息。一旦找到,服務(wù)器端(server-side)程序把結(jié)果信息表示成(formulate)HTML形式,最后Web服務(wù)器把會(huì)它發(fā)送到你的Web瀏覽器。
簡(jiǎn)而言之,Web服務(wù)器只是簡(jiǎn)單的通過(guò)響應(yīng)(response)HTML頁(yè)面來(lái)處理HTTP請(qǐng)求(request)。
情景2:帶應(yīng)用程序服務(wù)器的Web服務(wù)器
情景2和情景1相同的是Web服務(wù)器還是把響應(yīng)(response)的產(chǎn)生委托(delegates)給腳本(譯者注:服務(wù)器端(server-side)程序)。然而,你可以把查找定價(jià)的商業(yè)邏輯(business logic)放到應(yīng)用程序服務(wù)器上。由于這種變化,此腳本只是簡(jiǎn)單的調(diào)用應(yīng)用程序服務(wù)器的查找服務(wù)(lookup service),而不是已經(jīng)知道如何查找數(shù)據(jù)然后表示為(formulate)一個(gè)響應(yīng)(response)。 這時(shí)當(dāng)該腳本程序產(chǎn)生HTML響應(yīng)(response)時(shí)就可以使用該服務(wù)的返回結(jié)果了。
在此情景中,應(yīng)用程序服務(wù)器提供(serves)了用于查詢產(chǎn)品的定價(jià)信息的商業(yè)邏輯。(服務(wù)器的)這種功能(functionality)沒(méi)有指出有關(guān)顯示和客戶端如何使用此信息的細(xì)節(jié),相反客戶端和應(yīng)用程序服務(wù)器只是來(lái)回傳送數(shù)據(jù)。當(dāng)有客戶端調(diào)用應(yīng)用程序服務(wù)器的查找服務(wù)(lookup service)時(shí),此服務(wù)只是簡(jiǎn)單的查找并返回結(jié)果給客戶端。
通過(guò)從響應(yīng)產(chǎn)生(response-generating)HTML的代碼中分離出來(lái),在應(yīng)用程序之中該定價(jià)(查找)邏輯的可重用性更強(qiáng)了。其他的客戶端,例如收款機(jī),也可以調(diào)用同樣的服務(wù)(service)來(lái)作為一個(gè)店員給客戶結(jié)帳。相反,在情景1中的定價(jià)查找服務(wù)是不可重用的因?yàn)樾畔?nèi)嵌在HTML頁(yè)中了。
總而言之,在情景2的模型中,在Web服務(wù)器通過(guò)回應(yīng)HTML頁(yè)面來(lái)處理HTTP請(qǐng)求(request),而應(yīng)用程序服務(wù)器則是通過(guò)處理定價(jià)和有效性(availability)請(qǐng)求(request)來(lái)提供應(yīng)用程序邏輯的。
警告(Caveats)
現(xiàn)在,XML Web Services已經(jīng)使應(yīng)用程序服務(wù)器和Web服務(wù)器的界線混淆了。通過(guò)傳送一個(gè)XML有效載荷(payload)給服務(wù)器,Web服務(wù)器現(xiàn)在可以處理數(shù)據(jù)和響應(yīng)(response)的能力與以前的應(yīng)用程序服務(wù)器同樣多了。
另外,現(xiàn)在大多數(shù)應(yīng)用程序服務(wù)器也包含了Web服務(wù)器,這就意味著可以把Web服務(wù)器當(dāng)作是應(yīng)用程序服務(wù)器的一個(gè)子集(subset)。雖然應(yīng)用程序服務(wù)器包含了Web服務(wù)器的功能,但是開(kāi)發(fā)者很少把應(yīng)用程序服務(wù)器部署(deploy)成這種功能(capacity)(譯者注:這種功能是指既有應(yīng)用程序服務(wù)器的功能又有Web服務(wù)器的功能)。相反,如果需要,他們通常會(huì)把Web服務(wù)器獨(dú)立配置,和應(yīng)用程序服務(wù)器一前一后。這種功能的分離有助于提高性能(簡(jiǎn)單的Web請(qǐng)求(request)就不會(huì)影響應(yīng)用程序服務(wù)器了),分開(kāi)配置(專(zhuān)門(mén)的Web服務(wù)器,集群(clustering)等等),而且給最佳產(chǎn)品的選取留有余地。
下面的這個(gè)簡(jiǎn)單的 Java 程序完成四項(xiàng)不相關(guān)的任務(wù)。這樣的程序有單個(gè)控制線程,控制在這四個(gè)任務(wù)之間線性地移動(dòng)。此外,因?yàn)樗璧馁Y源 ? 打印機(jī)、磁盤(pán)、數(shù)據(jù)庫(kù)和顯示屏 — 由于硬件和軟件的限制都有內(nèi)在的潛伏時(shí)間,所以每項(xiàng)任務(wù)都包含明顯的等待時(shí)間。因此,程序在訪問(wèn)數(shù)據(jù)庫(kù)之前必須等待打印機(jī)完成打印文件的任務(wù),等等。如果您正在等待程序的完成,則這是對(duì)計(jì)算資源和您的時(shí)間的一種拙劣使用。改進(jìn)此程序的一種方法是使它成為多線程的。
四項(xiàng)不相關(guān)的任務(wù)
class myclass {
static public void main(String args[]) {
print_a_file();
manipulate_another_file();
access_database();
draw_picture_on_screen();
}
}
多個(gè)進(jìn)程
在大多數(shù)操作系統(tǒng)中都可以創(chuàng)建多個(gè)進(jìn)程。當(dāng)一個(gè)程序啟動(dòng)時(shí),它可以為即將開(kāi)始的每項(xiàng)任務(wù)創(chuàng)建一個(gè)進(jìn)程,并允許它們同時(shí)運(yùn)行。當(dāng)一個(gè)程序因等待網(wǎng)絡(luò)訪問(wèn)或用戶輸入而被阻塞時(shí),另一個(gè)程序還可以運(yùn)行,這樣就增加了資源利用率。但是,按照這種方式創(chuàng)建每個(gè)進(jìn)程要付出一定的代價(jià):設(shè)置一個(gè)進(jìn)程要占用相當(dāng)一部分處理器時(shí)間和內(nèi)存資源。而且,大多數(shù)操作系統(tǒng)不允許進(jìn)程訪問(wèn)其他進(jìn)程的內(nèi)存空間。因此,進(jìn)程間的通信很不方便,并且也不會(huì)將它自己提供給容易的編程模型。
線程
線程也稱(chēng)為輕型進(jìn)程 (LWP)。因?yàn)榫€程只能在單個(gè)進(jìn)程的作用域內(nèi)活動(dòng),所以創(chuàng)建線程比創(chuàng)建進(jìn)程要廉價(jià)得多。這樣,因?yàn)榫€程允許協(xié)作和數(shù)據(jù)交換,并且在計(jì)算資源方面非常廉價(jià),所以線程比進(jìn)程更可取。線程需要操作系統(tǒng)的支持,因此不是所有的機(jī)器都提供線程。Java 編程語(yǔ)言,作為相當(dāng)新的一種語(yǔ)言,已將線程支持與語(yǔ)言本身合為一體,這樣就對(duì)線程提供了強(qiáng)健的支持。
使用 Java 編程語(yǔ)言實(shí)現(xiàn)線程
Java編程語(yǔ)言使多線程如此簡(jiǎn)單有效,以致于某些程序員說(shuō)它實(shí)際上是自然的。盡管在 Java 中使用線程比在其他語(yǔ)言中要容易得多,仍然有一些概念需要掌握。要記住的一件重要的事情是 main() 函數(shù)也是一個(gè)線程,并可用來(lái)做有用的工作。程序員只有在需要多個(gè)線程時(shí)才需要?jiǎng)?chuàng)建新的線程。
Thread 類(lèi)
下面的代碼說(shuō)明了它的用法:
創(chuàng)建兩個(gè)新線程
import java.util.*;
class TimePrinter extends Thread {
int pauseTime;
String name;
public TimePrinter(int x, String n) {
pauseTime = x;
name = n;
}
public void run() {
while(true) {
try {
System.out.println(name + “:” + new
Date(System.currentTimeMillis()));
Thread.sleep(pauseTime);
} catch(Exception e) {
System.out.println(e);
}
}
}
static public void main(String args[]) {
TimePrinter tp1 = new TimePrinter(1000, “Fast Guy”);
tp1.start();
TimePrinter tp2 = new TimePrinter(3000, “Slow Guy”);
tp2.start();
}
}
在本例中,我們可以看到一個(gè)簡(jiǎn)單的程序,它按兩個(gè)不同的時(shí)間間隔(1 秒和 3 秒)在屏幕上顯示當(dāng)前時(shí)間。這是通過(guò)創(chuàng)建兩個(gè)新線程來(lái)完成的,包括 main() 共三個(gè)線程。但是,因?yàn)橛袝r(shí)要作為線程運(yùn)行的類(lèi)可能已經(jīng)是某個(gè)類(lèi)層次的一部分,所以就不能再按這種機(jī)制創(chuàng)建線程。雖然在同一個(gè)類(lèi)中可以實(shí)現(xiàn)任意數(shù)量的接口,但 Java 編程語(yǔ)言只允許一個(gè)類(lèi)有一個(gè)父類(lèi)。同時(shí),某些程序員避免從 Thread 類(lèi)導(dǎo)出,因?yàn)樗鼜?qiáng)加了類(lèi)層次。對(duì)于這種情況,就要 runnable 接口。
Runnable 接口
此接口只有一個(gè)函數(shù),run(),此函數(shù)必須由實(shí)現(xiàn)了此接口的類(lèi)實(shí)現(xiàn)。但是,就運(yùn)行這個(gè)類(lèi)而論,其語(yǔ)義與前一個(gè)示例稍有不同。我們可以用 runnable 接口改寫(xiě)前一個(gè)示例。(不同的部分用黑體表示。)
創(chuàng)建兩個(gè)新線程而不強(qiáng)加類(lèi)層次
import java.util.*;
class TimePrinter implements Runnable {
int pauseTime;
String name;
public TimePrinter(int x, String n) {
pauseTime = x;
name = n;
}
public void run() {
while(true) {
try {
System.out.println(name + “:” + new
Date(System.currentTimeMillis()));
Thread.sleep(pauseTime);
} catch(Exception e) {
System.out.println(e);
}
}
}
static public void main(String args[]) {
Thread t1 = new Thread(new TimePrinter(1000, “Fast Guy”));
t1.start();
Thread t2 = new Thread(new TimePrinter(3000, “Slow Guy”));
t2.start();
}
}
請(qǐng)注意,當(dāng)使用 runnable 接口時(shí),您不能直接創(chuàng)建所需類(lèi)的對(duì)象并運(yùn)行它; 必須從 Thread 類(lèi)的一個(gè)實(shí)例內(nèi)部運(yùn)行它。許多程序員更喜歡 runnable 接口,因?yàn)閺?Thread 類(lèi)繼承會(huì)強(qiáng)加類(lèi)層次。
synchronized 關(guān)鍵字
到目前為止,我們看到的示例都只是以非常簡(jiǎn)單的方式來(lái)利用線程。只有最小的數(shù)據(jù)流,而且不會(huì)出現(xiàn)兩個(gè)線程訪問(wèn)同一個(gè)對(duì)象的情況。但是,在大多數(shù)有用的程序中,線程之間通常有信息流。試考慮一個(gè)金融應(yīng)用程序,它有一個(gè) Account 對(duì)象,如下例中所示:
一個(gè)銀行中的多項(xiàng)活動(dòng)
public class Account {
String holderName;
float amount;
public Account(String name, float amt) {
holderName = name;
amount = amt;
}
public void deposit(float amt) {
amount += amt;
}
public void withdraw(float amt) {
amount -= amt;
}
public float checkBalance() {
return amount;
}
}
在此代碼樣例中潛伏著一個(gè)錯(cuò)誤。如果此類(lèi)用于單線程應(yīng)用程序,不會(huì)有任何問(wèn)題。但是,在多線程應(yīng)用程序的情況中,不同的線程就有可能同時(shí)訪問(wèn)同一個(gè) Account 對(duì)象,比如說(shuō)一個(gè)聯(lián)合帳戶的所有者在不同的 ATM 上同時(shí)進(jìn)行訪問(wèn)。在這種情況下,存入和支出就可能以這樣的方式發(fā)生:一個(gè)事務(wù)被另一個(gè)事務(wù)覆蓋。這種情況將是災(zāi)難性的。但是,Java 編程語(yǔ)言提供了一種簡(jiǎn)單的機(jī)制來(lái)防止發(fā)生這種覆蓋。每個(gè)對(duì)象在運(yùn)行時(shí)都有一個(gè)關(guān)聯(lián)的鎖。這個(gè)鎖可通過(guò)為方法添加關(guān)鍵字 synchronized 來(lái)獲得。這樣,修訂過(guò)的 Account 對(duì)象(如下所示)將不會(huì)遭受像數(shù)據(jù)損壞這樣的錯(cuò)誤:
對(duì)一個(gè)銀行中的多項(xiàng)活動(dòng)進(jìn)行同步處理
public class Account {
String holderName;
float amount;
public Account(String name, float amt) {
holderName = name;
amount = amt;
}
public synchronized void deposit(float amt) {
amount += amt;
}
public synchronized void withdraw(float amt) {
amount -= amt;
}
public float checkBalance() {
return amount;
}
}
deposit() 和 withdraw() 函數(shù)都需要這個(gè)鎖來(lái)進(jìn)行操作,所以當(dāng)一個(gè)函數(shù)運(yùn)行時(shí),另一個(gè)函數(shù)就被阻塞。請(qǐng)注意, checkBalance() 未作更改,它嚴(yán)格是一個(gè)讀函數(shù)。因?yàn)?checkBalance() 未作同步處理,所以任何其他方法都不會(huì)阻塞它,它也不會(huì)阻塞任何其他方法,不管那些方法是否進(jìn)行了同步處理。
Java 編程語(yǔ)言中的高級(jí)多線程支持
線程組
線程是被個(gè)別創(chuàng)建的,但可以將它們歸類(lèi)到線程組中,以便于調(diào)試和監(jiān)視。只能在創(chuàng)建線程的同時(shí)將它與一個(gè)線程組相關(guān)聯(lián)。在使用大量線程的程序中,使用線程組組織線程可能很有幫助??梢詫⑺鼈兛醋魇怯?jì)算機(jī)上的目錄和文件結(jié)構(gòu)。
線程間發(fā)信
當(dāng)線程在繼續(xù)執(zhí)行前需要等待一個(gè)條件時(shí),僅有 synchronized 關(guān)鍵字是不夠的。雖然 synchronized 關(guān)鍵字阻止并發(fā)更新一個(gè)對(duì)象,但它沒(méi)有實(shí)現(xiàn)線程間發(fā)信。Object 類(lèi)為此提供了三個(gè)函數(shù):wait()、notify() 和 notifyAll()。以全球氣候預(yù)測(cè)程序?yàn)槔?。這些程序通過(guò)將地球分為許多單元,在每個(gè)循環(huán)中,每個(gè)單元的計(jì)算都是隔離進(jìn)行的,直到這些值趨于穩(wěn)定,然后相鄰單元之間就會(huì)交換一些數(shù)據(jù)。所以,從本質(zhì)上講,在每個(gè)循環(huán)中各個(gè)線程都必須等待所有線程完成各自的任務(wù)以后才能進(jìn)入下一個(gè)循環(huán)。這個(gè)模型稱(chēng)為屏蔽同步,下例說(shuō)明了這個(gè)模型:
屏蔽同步
public class BSync {
int totalThreads;
int currentThreads;
public BSync(int x) {
totalThreads = x;
currentThreads = 0;
}
public synchronized void waitForAll() {
currentThreads++;
if(currentThreads < totalThreads) {
try {
wait();
} catch (Exception e) {}
}
else {
currentThreads = 0;
notifyAll();
}
}
}
當(dāng)對(duì)一個(gè)線程調(diào)用 wait() 時(shí),該線程就被有效阻塞,只到另一個(gè)線程對(duì)同一個(gè)對(duì)象調(diào)用 notify() 或 notifyAll() 為止。因此,在前一個(gè)示例中,不同的線程在完成它們的工作以后將調(diào)用 waitForAll() 函數(shù),最后一個(gè)線程將觸發(fā) notifyAll() 函數(shù),該函數(shù)將釋放所有的線程。第三個(gè)函數(shù) notify() 只通知一個(gè)正在等待的線程,當(dāng)對(duì)每次只能由一個(gè)線程使用的資源進(jìn)行訪問(wèn)限制時(shí),這個(gè)函數(shù)很有用。但是,不可能預(yù)知哪個(gè)線程會(huì)獲得這個(gè)通知,因?yàn)檫@取決于 Java 虛擬機(jī) (JVM) 調(diào)度算法。
將 CPU 讓給另一個(gè)線程
當(dāng)線程放棄某個(gè)稀有的資源(如數(shù)據(jù)庫(kù)連接或網(wǎng)絡(luò)端口)時(shí),它可能調(diào)用 yield() 函數(shù)臨時(shí)降低自己的優(yōu)先級(jí),以便某個(gè)其他線程能夠運(yùn)行。
守護(hù)線程
有兩類(lèi)線程:用戶線程和守護(hù)線程。用戶線程是那些完成有用工作的線程。 守護(hù)線程是那些僅提供輔助功能的線程。Thread 類(lèi)提供了 setDaemon() 函數(shù)。Java 程序?qū)⑦\(yùn)行到所有用戶線程終止,然后它將破壞所有的守護(hù)線程。在 Java 虛擬機(jī) (JVM) 中,即使在 main 結(jié)束以后,如果另一個(gè)用戶線程仍在運(yùn)行,則程序仍然可以繼續(xù)運(yùn)行。
避免不提倡使用的方法
不提倡使用的方法是為支持向后兼容性而保留的那些方法,它們?cè)谝院蟮陌姹局锌赡艹霈F(xiàn),也可能不出現(xiàn)。Java 多線程支持在版本 1.1 和版本 1.2 中做了重大修訂,stop()、suspend() 和 resume() 函數(shù)已不提倡使用。這些函數(shù)在 JVM 中可能引入微妙的錯(cuò)誤。雖然函數(shù)名可能聽(tīng)起來(lái)很誘人,但請(qǐng)抵制誘惑不要使用它們。
調(diào)試線程化的程序
在線程化的程序中,可能發(fā)生的某些常見(jiàn)而討厭的情況是死鎖、活鎖、內(nèi)存損壞和資源耗盡。
死鎖
死鎖可能是多線程程序最常見(jiàn)的問(wèn)題。當(dāng)一個(gè)線程需要一個(gè)資源而另一個(gè)線程持有該資源的鎖時(shí),就會(huì)發(fā)生死鎖。這種情況通常很難檢測(cè)。但是,解決方案卻相當(dāng)好:在所有的線程中按相同的次序獲取所有資源鎖。例如,如果有四個(gè)資源 ?A、B、C 和 D ? 并且一個(gè)線程可能要獲取四個(gè)資源中任何一個(gè)資源的鎖,則請(qǐng)確保在獲取對(duì) B 的鎖之前首先獲取對(duì) A 的鎖,依此類(lèi)推。如果“線程 1”希望獲取對(duì) B 和 C 的鎖,而“線程 2”獲取了 A、C 和 D 的鎖,則這一技術(shù)可能導(dǎo)致阻塞,但它永遠(yuǎn)不會(huì)在這四個(gè)鎖上造成死鎖。
活鎖
當(dāng)一個(gè)線程忙于接受新任務(wù)以致它永遠(yuǎn)沒(méi)有機(jī)會(huì)完成任何任務(wù)時(shí),就會(huì)發(fā)生活鎖。這個(gè)線程最終將超出緩沖區(qū)并導(dǎo)致程序崩潰。試想一個(gè)秘書(shū)需要錄入一封信,但她一直在忙于接電話,所以這封信永遠(yuǎn)不會(huì)被錄入。
內(nèi)存損壞
如果明智地使用 synchronized 關(guān)鍵字,則完全可以避免內(nèi)存錯(cuò)誤這種氣死人的問(wèn)題。
資源耗盡
某些系統(tǒng)資源是有限的,如文件描述符。多線程程序可能耗盡資源,因?yàn)槊總€(gè)線程都可能希望有一個(gè)這樣的資源。如果線程數(shù)相當(dāng)大,或者某個(gè)資源的侯選線程數(shù)遠(yuǎn)遠(yuǎn)超過(guò)了可用的資源數(shù),則最好使用資源池。一個(gè)最好的示例是數(shù)據(jù)庫(kù)連接池。只要線程需要使用一個(gè)數(shù)據(jù)庫(kù)連接,它就從池中取出一個(gè),使用以后再將它返回池中。資源池也稱(chēng)為 資源庫(kù)。
調(diào)試大量的線程
有時(shí)一個(gè)程序因?yàn)橛写罅康木€程在運(yùn)行而極難調(diào)試。在這種情況下,下面的這個(gè)類(lèi)可能會(huì)派上用場(chǎng):
public class Probe extends Thread {
public Probe() {}
public void run() {
while(true) {
Thread[] x = new Thread[100];
Thread.enumerate(x);
for(int i=0; i<100; i++) {
Thread t = x[i];
if(t == null)
break;
else
System.out.println(t.getName() + “\t” + t.getPriority()
+ “\t” + t.isAlive() + “\t” + t.isDaemon());
}
}
}
}
限制線程優(yōu)先級(jí)和調(diào)度
Java 線程模型涉及可以動(dòng)態(tài)更改的線程優(yōu)先級(jí)。本質(zhì)上,線程的優(yōu)先級(jí)是從 1 到 10 之間的一個(gè)數(shù)字,數(shù)字越大表明任務(wù)越緊急。JVM 標(biāo)準(zhǔn)首先調(diào)用優(yōu)先級(jí)較高的線程,然后才調(diào)用優(yōu)先級(jí)較低的線程。但是,該標(biāo)準(zhǔn)對(duì)具有相同優(yōu)先級(jí)的線程的處理是隨機(jī)的。如何處理這些線程取決于基層的操作系統(tǒng)策略。在某些情況下,優(yōu)先級(jí)相同的線程分時(shí)運(yùn)行; 在另一些情況下,線程將一直運(yùn)行到結(jié)束。請(qǐng)記住,Java 支持 10 個(gè)優(yōu)先級(jí),基層操作系統(tǒng)支持的優(yōu)先級(jí)可能要少得多,這樣會(huì)造成一些混亂。因此,只能將優(yōu)先級(jí)作為一種很粗略的工具使用。最后的控制可以通過(guò)明智地使用 yield() 函數(shù)來(lái)完成。通常情況下,請(qǐng)不要依靠線程優(yōu)先級(jí)來(lái)控制線程的狀態(tài)。
第一范式
定義:如果關(guān)系R 中所有屬性的值域都是單純域,那么關(guān)系模式R是第一范式的
那么符合第一模式的特點(diǎn)就有
1)有主關(guān)鍵字
2)主鍵不能為空,
3)主鍵不能重復(fù),
4)字段不可以再分
例如:
StudyNo | Name | Sex | Contact
20040901 john Male Email:kkkk@ee.net,phone:222456
20040901 mary famale email:kkk@fff.net phone:123455
以上的表就不符合,第一范式:主鍵重復(fù)(實(shí)際中數(shù)據(jù)庫(kù)不允許重復(fù)的),而且Contact字段可以再分
所以變更為正確的是
StudyNo | Name | Sex | Email | Phone
20040901 john Male kkkk@ee.net 222456
20040902 mary famale kkk@fff.net 123455
第二范式:
定義:如果關(guān)系模式R是第一范式的,而且關(guān)系中每一個(gè)非主屬性不部分依賴(lài)于主鍵,稱(chēng)R是第二范式的。
所以第二范式的主要任務(wù)就是
滿足第一范式的前提下,消除部分函數(shù)依賴(lài)。
StudyNo | Name | Sex | Email | Phone | ClassNo | ClassAddress
01 john Male kkkk@ee.net 222456 200401 A樓2
02 mary famale kkk@fff.net 123455 200402 A樓3
這個(gè)表完全滿足于第一范式,
主鍵由StudyNo和ClassNo組成,這樣才能定位到指定行
但是,ClassAddress部分依賴(lài)于關(guān)鍵字(ClassNo-〉ClassAddress),
所以要變?yōu)閮蓚€(gè)表
表一
StudyNo | Name | Sex | Email | Phone | ClassNo
01 john Male kkkk@ee.net 222456 200401
02 mary famale kkk@fff.net 123455 200402
表二
ClassNo | ClassAddress
200401 A樓2
200402 A樓3
第三范式:
滿足第二范式的前提下,消除傳遞依賴(lài)。
例:
StudyNo | Name | Sex | Email | bounsLevel | bouns
20040901 john Male kkkk@ee.net 優(yōu)秀 $1000
20040902 mary famale kkk@fff.net 良 $600
這個(gè)完全滿足了第二范式,但是bounsLevel和bouns存在傳遞依賴(lài)
更改為:
StudyNo | Name | Sex | Email | bouunsNo
20040901 john Male kkkk@ee.net 1
20040902 mary famale kkk@fff.net 2
bounsNo | bounsLevel | bouns
1 優(yōu)秀 $1000
2 良 $600
這里我比較喜歡用bounsNo作為主鍵,
基于兩個(gè)原因
1)不要用字符作為主鍵??赡苡腥苏f(shuō):如果我的等級(jí)一開(kāi)始就用數(shù)值就代替呢?
2)但是如果等級(jí)名稱(chēng)更改了,不叫 1,2 ,3或優(yōu)、良,這樣就可以方便更改,所以我一般優(yōu)先使用與業(yè)務(wù)無(wú)關(guān)的字段作為關(guān)鍵字。
一般滿足前三個(gè)范式就可以避免數(shù)據(jù)冗余。
第四范式:
主要任務(wù):滿足第三范式的前提下,消除多值依賴(lài)
product | agent | factory
Car A1 F1
Bus A1 F2
Car A2 F2
在這里,Car的定位,必須由 agent 和 Factory才能得到(所以主鍵由agent和factory組成),可以通過(guò) product依賴(lài)了agent和factory兩個(gè)屬性
所以正確的是
表1 表2:
product | agent factory | product
Car A1 F1 Car
Bus A1 F2 Car
Car A2 F2 Bus
第五范式:
定義: 如果關(guān)系模式R中的每一個(gè)連接依賴(lài), 都是由R的候選鍵所蘊(yùn)含, 稱(chēng)R是第五范式的
看到定義,就知道是要消除連接依賴(lài),并且必須保證數(shù)據(jù)完整
例子
A | B | C
a1 b1 c1
a2 b1 c2
a1 b2 c1
a2 b2 c2
如果要定位到特定行,必須三個(gè)屬性都為關(guān)鍵字。
所以關(guān)系要變?yōu)?三個(gè)關(guān)系,分別是A 和B,B和C ,C和A
如下:
表1 表2 表3
A | B B | C C | A
a1 b1 b1 c1 c1 a1
a1 b2 b1 c2 c1 a2
數(shù)據(jù)庫(kù)范式是數(shù)據(jù)庫(kù)設(shè)計(jì)中必不可少的知識(shí),沒(méi)有對(duì)范式的理解,就無(wú)法設(shè)計(jì)出高效率、優(yōu)雅的數(shù)據(jù)庫(kù)。甚至設(shè)計(jì)出錯(cuò)誤的數(shù)據(jù)庫(kù)。而想要理解并掌握范式卻并不是那 么容易。教科書(shū)中一般以關(guān)系代數(shù)的方法來(lái)解釋數(shù)據(jù)庫(kù)范式。這樣做雖然能夠十分準(zhǔn)確的表達(dá)數(shù)據(jù)庫(kù)范式,但比較抽象,不太直觀,不便于理解,更難以記憶。
一、基礎(chǔ)概念
- 實(shí)體:現(xiàn)實(shí)世界中客觀存在并可以被區(qū)別的事物。比如“一個(gè)學(xué)生”、“一本書(shū)”、“一門(mén)課”等等。值得強(qiáng)調(diào)的是這里所說(shuō)的“事物”不僅僅是看得見(jiàn)摸得著的“東西”,它也可以是虛擬的,不如說(shuō)“老師與學(xué)校的關(guān)系”。
- 屬性:教科書(shū)上解釋為:“實(shí)體所具有的某一特性”,由此可見(jiàn),屬性一開(kāi)始是個(gè)邏輯概念,比如說(shuō),“性別”是“人”的一個(gè)屬性。在關(guān)系數(shù)據(jù)庫(kù)中,屬性又是個(gè)物理概念,屬性可以看作是“表的一列”。
- 元組:表中的一行就是一個(gè)元組。
- 分量:元組的某個(gè)屬性值。在一個(gè)關(guān)系數(shù)據(jù)庫(kù)中,它是一個(gè)操作原子,即關(guān)系數(shù)據(jù)庫(kù)在做任何操作的時(shí)候,屬性是“不可分的”。否則就不是關(guān)系數(shù)據(jù)庫(kù)了。
- 碼:表中可以唯一確定一個(gè)元組的某個(gè)屬性(或者屬性組),如果這樣的碼有不止一個(gè),那么大家都叫候選碼,我們從候選碼中挑一個(gè)出來(lái)做老大,它就叫主碼。
- 全碼:如果一個(gè)碼包含了所有的屬性,這個(gè)碼就是全碼。
- 主屬性:一個(gè)屬性只要在任何一個(gè)候選碼中出現(xiàn)過(guò),這個(gè)屬性就是主屬性。
- 非主屬性:與上面相反,沒(méi)有在任何候選碼中出現(xiàn)過(guò),這個(gè)屬性就是非主屬性。
- 外碼:一個(gè)屬性(或?qū)傩越M),它不是碼,但是它別的表的碼,它就是外碼。
二、6個(gè)范式
好了,上面已經(jīng)介紹了我們掌握范式所需要的全部基礎(chǔ)概念,下面我們就來(lái)講范式。首先要明白,范式的包含關(guān)系。一個(gè)數(shù)據(jù)庫(kù)設(shè)計(jì)如果符合第二范式,一定也符合第一范式。如果符合第三范式,一定也符合第二范式…
第一范式(1NF):屬性不可分。
在前面我們已經(jīng)介紹了
屬性值的概念,我們說(shuō),它是“不可分的”。而第一范式要求屬性也不可分。那么它和屬性值不可分有什么區(qū)別呢?給一個(gè)例子:
name |
tel |
age |
大寶 |
13612345678 |
22 |
小明 |
13988776655 |
010-1234567 |
21 |
Ps:這個(gè)表中,屬性值“分”了。
name |
tel |
age |
手機(jī) |
座機(jī) |
大寶 |
13612345678 |
021-9876543 |
22 |
小明 |
13988776655 |
010-1234567 |
21 |
Ps:這個(gè)表中,屬性 “分”了。
這兩種情況都不滿足第一范式。不滿足第一范式的數(shù)據(jù)庫(kù),不是關(guān)系數(shù)據(jù)庫(kù)!所以,我們?cè)谌魏侮P(guān)系數(shù)據(jù)庫(kù)管理系統(tǒng)中,做不出這樣的“表”來(lái)。
第二范式(2NF):符合1NF,并且,
非主屬性完全依賴(lài)于碼。
聽(tīng)起來(lái)好像很神秘,其實(shí)真的沒(méi)什么。
一 個(gè)候選碼中的主屬性也可能是好幾個(gè)。如果一個(gè)主屬性,它不能單獨(dú)做為一個(gè)候選碼,那么它也不能確定任何一個(gè)非主屬性。給一個(gè)反例:我們考慮一個(gè)小學(xué)的教務(wù) 管理系統(tǒng),學(xué)生上課指定一個(gè)老師,一本教材,一個(gè)教室,一個(gè)時(shí)間,大家都上課去吧,沒(méi)有問(wèn)題。那么數(shù)據(jù)庫(kù)怎么設(shè)計(jì)?(學(xué)生上課表)
學(xué)生 |
課程 |
老師 |
老師職稱(chēng) |
教材 |
教室 |
上課時(shí)間 |
小明 |
一年級(jí)語(yǔ)文(上) |
大寶 |
副教授 |
《小學(xué)語(yǔ)文1》 |
101 |
14:30 |
一個(gè)學(xué)生上一門(mén)課,一定在特定某個(gè)教室。所以有(學(xué)生,課程)->教室
一個(gè)學(xué)生上一門(mén)課,一定是特定某個(gè)老師教。所以有(學(xué)生,課程)->老師
一個(gè)學(xué)生上一門(mén)課,他老師的職稱(chēng)可以確定。所以有(學(xué)生,課程)->老師職稱(chēng)
一個(gè)學(xué)生上一門(mén)課,一定是特定某個(gè)教材。所以有(學(xué)生,課程)->教材
一個(gè)學(xué)生上一門(mén)課,一定在特定時(shí)間。所以有(學(xué)生,課程)->上課時(shí)間
因此(學(xué)生,課程)是一個(gè)碼。
然而,一個(gè)課程,一定指定了某個(gè)教材,一年級(jí)語(yǔ)文肯定用的是《小學(xué)語(yǔ)文1》,那么就有課程->教材。(學(xué)生,課程)是個(gè)碼,課程卻決定了教材,這就叫做不完全依賴(lài),或者說(shuō)部分依賴(lài)。出現(xiàn)這樣的情況,就不滿足第二范式!
有什么不好嗎?你可以想想:
1、校長(zhǎng)要新增加一門(mén)課程叫“微積分”,教材是《大學(xué)數(shù)學(xué)》,怎么辦?學(xué)生還沒(méi)選課,而學(xué)生又是主屬性,主屬性不能空,課程怎么記錄呢,教材記到哪呢? ……郁悶了吧?
(插入異常)
2、下學(xué)期沒(méi)學(xué)生學(xué)一年級(jí)語(yǔ)文(上)了,學(xué)一年級(jí)語(yǔ)文(下)去了,那么表中將不存在一年級(jí)語(yǔ)文(上),也就沒(méi)了《小學(xué)語(yǔ)文1》。這時(shí)候,校長(zhǎng)問(wèn):一年級(jí)語(yǔ)文(上)用的什么教材啊?……郁悶了吧?
(刪除異常)
3、校長(zhǎng)說(shuō):一年級(jí)語(yǔ)文(上)換教材,換成《大學(xué)語(yǔ)文》。有10000個(gè)學(xué)生選了這么課,改動(dòng)好大??!改累死了……郁悶了吧?
(修改異常)
那應(yīng)該怎么解決呢?投影分解,將一個(gè)表分解成兩個(gè)或若干個(gè)表
學(xué)生 |
課程 |
老師 |
老師職稱(chēng) |
教室 |
上課時(shí)間 |
小明 |
一年級(jí)語(yǔ)文(上) |
大寶 |
副教授 |
101 |
14:30 |
學(xué)生上課表新
課程 |
教材 |
一年級(jí)語(yǔ)文(上) |
《小學(xué)語(yǔ)文1》 |
課程的表
第三范式(3NF):符合2NF,并且,
消除傳遞依賴(lài)
上面的“學(xué)生上課表新”符合2NF,可以這樣驗(yàn)證:兩個(gè)主屬性單獨(dú)使用,不用確定其它四個(gè)非主屬性的任何一個(gè)。但是它有傳遞依賴(lài)!
在哪呢?問(wèn)題就出在“老師”和“老師職稱(chēng)”這里。一個(gè)老師一定能確定一個(gè)老師職稱(chēng)。
有什么問(wèn)題嗎?想想:
1、老師升級(jí)了,變教授了,要改數(shù)據(jù)庫(kù),表中有N條,改了N次……
(修改異常)
2、沒(méi)人選這個(gè)老師的課了,老師的職稱(chēng)也沒(méi)了記錄……
(刪除異常)
3、新來(lái)一個(gè)老師,還沒(méi)分配教什么課,他的職稱(chēng)記到哪?……
(插入異常)
那應(yīng)該怎么解決呢?和上面一樣,投影分解:
學(xué)生 |
課程 |
老師 |
教室 |
上課時(shí)間 |
小明 |
一年級(jí)語(yǔ)文(上) |
大寶 |
101 |
14:30 |
BC范式(BCNF):符合3NF,并且,
主屬性不依賴(lài)于主屬性
若關(guān)系模式屬于第一范式,且每個(gè)屬性都不傳遞依賴(lài)于鍵碼,則R屬于BC范式。
通常
BC范式的條件有多種等價(jià)的表述:每個(gè)非平凡依賴(lài)的左邊必須包含鍵碼;每個(gè)決定因素必須包含鍵碼。
BC范式既檢查非主屬性,又檢查主屬性。當(dāng)只檢查非主屬性時(shí),就成了第三范式。滿足BC范式的關(guān)系都必然滿足第三范式。
還可以這么說(shuō):
若一個(gè)關(guān)系達(dá)到了第三范式,并且它只有一個(gè)候選碼,或者它的每個(gè)候選碼都是單屬性,則該關(guān)系自然達(dá)到BC范式。
一般,一個(gè)數(shù)據(jù)庫(kù)設(shè)計(jì)符合3NF或BCNF就可以了。在BC范式以上還有第四范式、第五范式。
第四范式:要求把同一表內(nèi)的多對(duì)多關(guān)系刪除。
第五范式:從最終結(jié)構(gòu)重新建立原始結(jié)構(gòu)。
關(guān)鍵字: editplus
原文出自:http://www.cnblogs.com/JustinYoung/archive/2008/01/14/editplus-skills.html
除了windows操作系統(tǒng),EditPlus可以說(shuō)是我最經(jīng)常使用的軟件了。無(wú)論是編寫(xiě)xhtml頁(yè)面,還是css、js文件,甚至隨筆記記這樣的事情,我都會(huì)使用EditPlus(現(xiàn)在使用的是EditPlus2.31英文版),感覺(jué)它不僅功能強(qiáng)大,更難得的是:綠色、輕量級(jí)、啟動(dòng)速度快、穩(wěn)定性高……反正,我個(gè)人是愛(ài)死她了
在使用中,我個(gè)人也總結(jié)了一些使用經(jīng)驗(yàn)??赡茏鳛楦呤值哪?,看來(lái)只是”相當(dāng)膚淺”,但是沒(méi)有關(guān)系,因?yàn)槲蚁嘈牛灰阎R(shí)共享出來(lái),總能幫助到一些還在進(jìn)步中的朋友。下面就讓我們來(lái)開(kāi)始配置出符合你自己使用習(xí)慣的EditPlus吧!
一邊閱讀,一邊動(dòng)手吧!
為了達(dá)到更好的效果,請(qǐng)你先下載我打包的這個(gè) EditPlus壓縮包文件(壓縮包文件為綠色的EditPlus2.31英文版,含自動(dòng)完成文件,高亮語(yǔ)法文件和剪切板代碼片斷文件,這些文件在解壓目錄下的”yzyFile”目錄下),這樣就可以一邊看著這篇文章,一邊親自動(dòng)手,從而達(dá)到更好的效果了。
設(shè)置EditPlus的配置文件路徑
因?yàn)镋ditPlus是可以綠色使用的(直接解壓那個(gè)EditPlus壓縮包文件即可直接使用,不用安裝),所以,當(dāng)我們對(duì)EditPlus進(jìn)行一系列的配置以后,保存下這些配置文件。以后當(dāng)我們重裝系統(tǒng),或者換臺(tái)電腦使用的時(shí)候,只要重新加載一下那些配置文件,以前的配置就都重新回來(lái)了,很是方便。所以,在講其他配置和技巧之前,我們先設(shè)置好EditPlus的配置文件路徑。
打開(kāi)EditPlus → 【Tools】→ 【INI File Directory…】 → 在彈出的對(duì)話框中設(shè)置配置文件的保存位置(壓縮包內(nèi)的配置保存文件在解壓目錄下的”yzyFile\INIFiles”目錄下)。這里你可能要重新設(shè)置一下目錄,因?yàn)?,我喜歡把EditPlus放在”D:\GreenSoft\EditPlus 2″下(把所有的綠色軟件裝在一個(gè)目錄下,每次重裝系統(tǒng)的時(shí)候,可以直接把綠色軟件拷回去,就能直接使用了,從而避免了每次都安裝那么多軟件)。所以,就請(qǐng)你重新設(shè)置一下,根據(jù)你的習(xí)慣,把配置文件存放在某個(gè)目錄下吧。

圖1:設(shè)置EditPlus的配置文件保存路徑
保護(hù)視力,從EditPlus做起
“最近眼睛好痛呀!”、”靠~眼睛簡(jiǎn)直要瞎了!”……不知道作為程序員的你是否也經(jīng)常抱怨這樣的事情,每天對(duì)著電腦看,的確對(duì)視力的傷害很大,所以能不能采取一些措施來(lái)為眼睛減減壓呢?我在EditPlus里面是這樣做的(因?yàn)镋ditPlus是我最長(zhǎng)使用的工具,所以以EditPlus為例)–編輯區(qū)的背景設(shè)為灰色而不是默認(rèn)的白色,使用較大字號(hào)的字體。效果如下圖所示:

圖2:灰色的背景,12號(hào)的雅黑字體,構(gòu)造”愛(ài)眼”環(huán)境你可以這樣設(shè)置EditPlus編輯環(huán)境的背景顏色和字體。菜單【Tools】→ 【Preperences】→ 【General】→ 【fonts】和【colors】。需要說(shuō)明一下的是:可以設(shè)置多種fonts,這樣就可以很方便地切換fonts了(參看圖2所示),這招對(duì)日企這樣的朋友很方便哦。中文的字體設(shè)置幾個(gè),日文的字體設(shè)置幾個(gè),出現(xiàn)亂碼的時(shí)候,切換一下字體就可以了。

圖3:設(shè)置EditPlus的字體和顏色
配置文件模板,告別重復(fù)的體力勞動(dòng)
設(shè)置好EditPlus的配置文件,就讓我們開(kāi)始EditPlus的使用技巧吧。第一個(gè)技巧當(dāng)然就是和”新建”有關(guān)的啦。如果我們經(jīng)常建立一種文件,而這種文件總會(huì)包含一些重復(fù)的文字或者代碼的話,我們就可以建立模板,然后通過(guò)模板建立文件。從而擺脫每次都要重復(fù)的體力勞動(dòng)。
我們就從建立一個(gè)屬于自己的xhtml文件開(kāi)始吧。菜單【File】→ 【New】→ 【Configure templates…】→ 在打開(kāi)的對(duì)話框中”填上”菜單中顯示的文字,已經(jīng)模板文件的路徑,就可以了。下次當(dāng)你再次選擇【File】→ 【New】的時(shí)候,就能夠看到你建立的模板了。

圖4:EditPlus中建立自己的模板模板文件要怎么建立呢?其實(shí)很簡(jiǎn)單呀。舉個(gè)很簡(jiǎn)單的例子,如果我想建立一個(gè)我自己的xhtml頁(yè)面,模板文件就可以寫(xiě)成這樣–
1
<!DOCTYPE html public ”-//W3C//DTD XHTML 1.0 Transitional//EN” ”http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>
2
<html xmlns=”http://www.w3.org/1999/xhtml”>
3
<head>
4
<meta http-equiv=”Content-Type” content=”text/html; charset=utf-8″ />
5
<meta name=”Keywords” content=”YES!B/S!,JustinYoung,web標(biāo)準(zhǔn)設(shè)計(jì)” />
6
<meta name=”Description” content=”This page is from http://Justinyoung.cnblogs.com” />
7
<title>簡(jiǎn)單的XHTML頁(yè)面</title>
8
</head>
9
<body>
10
^!
11
</body>
12
</html>顯然里面的Keywords和Description,意見(jiàn)title的內(nèi)容都已經(jīng)變成我常用的了。還有一點(diǎn),請(qǐng)大家注意第10行的”^!”標(biāo)簽。這個(gè)標(biāo)簽在EditPlus中表示光標(biāo)所在位置。顯然,這里的意思就是:當(dāng)你用這個(gè)模板建立一個(gè)新的文件的時(shí)候,光標(biāo)就會(huì)自動(dòng)停留在<body>和</body>之間,從而方便你的直接輸入。
關(guān)于模板文件再說(shuō)兩句:
1:在我提供的那個(gè) EditPlus壓縮包文件中,模板文件存放在解壓目錄下的”\yzyFile\Templates”文件夾下。
2:我們知道使用快捷鍵”Ctrl + Shift + N”可以快速的建立一個(gè)html頁(yè)面,而這個(gè)可以快速的建立html的模板,位于EditPlus目錄下的,文件名為”templatex.html”。你可以通過(guò)修改這個(gè)模板文件,來(lái)達(dá)到你個(gè)性化html頁(yè)面的目的。
順手的側(cè)邊欄
如果你看不到側(cè)邊欄,可以使用快捷鍵(Alt + Shift + 1)。側(cè)邊欄包含了”快速目錄路徑”和”快速剪貼板”功能。”快速目錄路徑”就不說(shuō)了,重點(diǎn)來(lái)說(shuō)說(shuō)”快速剪貼板”功能吧。其實(shí)說(shuō)白了,就是一個(gè)地方,這個(gè)地方可以存放一些代碼片斷、常用文言等等文字。當(dāng)你需要這些文字的時(shí)候,只要雙擊,就可以方便的添加到光標(biāo)所在位置了。默認(rèn)情況下會(huì)有一些html,css代碼,但是,說(shuō)實(shí)話,我是不太經(jīng)常使用那些東西的,那么多,找到都累死了。所以,我喜歡建立一個(gè)自己最常用的”剪貼板”庫(kù),因?yàn)槭亲约航ǖ?,所以用著就?huì)比較順手了。
你可以通過(guò)這種方式來(lái)建立自己的”剪貼板”庫(kù)文件。在Cliptext側(cè)邊欄上的下拉列表框上點(diǎn)擊右鍵 → 新建 → 填寫(xiě)文件名和顯示標(biāo)題→ 在新建的空白側(cè)邊欄上點(diǎn)擊右鍵 → 新建 → 填入顯示文本和代碼即可。
關(guān)于”剪貼板”庫(kù)文件再說(shuō)兩句:
1:在我提供的那個(gè) EditPlus壓縮包文件中”剪貼板”庫(kù)文件存放在解壓目錄下的”\yzyFile\CliptextLibrary”文件夾下。
2:你可以通過(guò)直接編輯,解壓目錄下的”\yzyFile\CliptextLibrary”文件夾下的”剪貼板”庫(kù)文件,來(lái)快速的建立自己的常用代碼庫(kù)(用EditPlus就可以打開(kāi),格式看一下就懂了。編輯好以后要重新”Reload”一下,或者重新啟動(dòng)一下才能刷新哦)。
3:側(cè)邊欄可以放在左邊,也可以放在右面。設(shè)置的方法是:在側(cè)邊欄點(diǎn)擊鼠標(biāo)右鍵 → 選擇【Location】菜單內(nèi)的left或者right。
華麗的自動(dòng)完成功能
<ul>
<li><a href=”" mce_href=”" title=”"></a></li>
<li><a href=”" mce_href=”" title=”"></a></li>
<li><a href=”" mce_href=”" title=”"></a></li>
<li><a href=”" mce_href=”" title=”"></a></li>
<li><a href=”" mce_href=”" title=”"></a></li>
</ul>可以說(shuō)是俺最喜歡的功能了。想象一下,作為一個(gè)經(jīng)常制作網(wǎng)頁(yè)的人來(lái)所,當(dāng)你打一個(gè)”ua”字,然后按下空格,編輯器里面就出現(xiàn)了右邊的代碼,而且鼠標(biāo)就停留在第一個(gè)href的雙引號(hào)之間。那是多么愉快的事情。這就是EditPlus的自動(dòng)完成功能,使用EditPlus的自動(dòng)完成功能將會(huì)極大的提高你的工作效率。而且我們可以根據(jù)不同的文件類(lèi)型,建立不同的”自動(dòng)完成”,例如,如果是xhtml文件,打”b”+ 空格”,就是 <strong></strong>,而在css文件中,”b”+ 空格”,就是 “border:1px solid red;”。非常的人性化。
你可以通過(guò)這樣的設(shè)置,來(lái)使用EditPlus的自動(dòng)完成功能?!綯ools】→【Preperences】→ 【Files】→ 【Settings & syntax】 → 在【File types】中設(shè)置一下文件類(lèi)型,然后再【Auto completion】中選擇自動(dòng)完成文件即可(如果你使用的是我那個(gè) EditPlus壓縮包文件,請(qǐng)注意調(diào)整這里的自動(dòng)完成文件的路徑)。自動(dòng)完成文件我們可以自己進(jìn)行編輯,這里我舉個(gè)簡(jiǎn)單的例子,展開(kāi)下面的代碼,這個(gè)便是我css文件自動(dòng)完成的文件內(nèi)容,以第11行的”#T=bor”為例,它的意思就是如果輸入bor然后按空格,就在光標(biāo)所在位置插入”border:1px solid red;”
關(guān)于”自動(dòng)完成”文件再說(shuō)兩句:
1:在我提供的那個(gè) EditPlus壓縮包文件中”自動(dòng)完成”文件存放在解壓目錄下的”\yzyFile\AutoCompletion”文件夾下。
2:你可以通過(guò)直接編輯,解壓目錄下的”\yzyFile\AutoCompletion”文件夾下的EditPlus自動(dòng)完成文件,來(lái)快速的建立自己的EditPlus自動(dòng)完成文件。

圖5:”自動(dòng)完成”和”高亮語(yǔ)法”設(shè)置對(duì)話框
彩色的文件,高亮語(yǔ)法文件
很多的開(kāi)發(fā)工具都有語(yǔ)法高亮顯示功能,EditPlus雖小,但是也有這個(gè)功能哦。設(shè)置方法可以參考圖片5所示。和”自動(dòng)完成”功能一樣,只要為不同的文件類(lèi)型指定”高亮語(yǔ)法”文件即可。css、html等常用的文件類(lèi)型,EditPlus已經(jīng)自帶了高亮語(yǔ)法文件。如果自帶的高亮語(yǔ)法文件沒(méi)有你需要的,你可以去EditPlus官方網(wǎng)站的文件下載頻道去看看,來(lái)自全球各地的朋友,貢獻(xiàn)了很多的不同文件類(lèi)型的高亮語(yǔ)法文件??梢院芊奖愕孛赓M(fèi)下載到。
這里就稍微列舉一下比較常用的EditPlus的高亮語(yǔ)法文件,更多的請(qǐng)到EditPlus的官方網(wǎng)站下載,EditPlus的官方地址為: http://www.editplus.com/files.html
EditPlus正則表達(dá)式
EditPlus中的查找(替換)功能,支持正則表達(dá)式。使用正則表達(dá)式可以極大的提高查找(替換)的強(qiáng)悍程度。因?yàn)檎齽t表達(dá)式這東西不是一句話就能說(shuō)完的,而且偏離此篇文章主題,所以這里只列舉幾個(gè)常用的例子。對(duì)此有興趣的可以參考正則表達(dá)式資料,或者在EditPlus的help中”Regular Expression”關(guān)鍵字進(jìn)行索引查找。

圖6:在查找(替換)對(duì)話框中使用正則表達(dá)式使用正則表達(dá)式進(jìn)行查找(替換)的方法如上圖所示,選擇查找(替換)對(duì)話框中”Regular Expression”前面checkbox。點(diǎn)擊查找(替換)文本框后的”倒三角”可以選擇常用的正則表達(dá)式。
正則表達(dá)式實(shí)例
需求說(shuō)明 |
正則表達(dá)式寫(xiě)法 |
備注 |
替換指定內(nèi)容(以abc為例)到行尾 |
abc.* |
“.”表示匹配任意字符;”*”表示匹配0次或更多 |
給所有的數(shù)字加上引號(hào) |
查找[0-9]替換為”\0″ |
\0表示正則表達(dá)式匹配的對(duì)象 |
刪除空白行 |
查找\n\n 替換為\n |
把連續(xù)的2個(gè)換行符,替換成一個(gè)換行符 |
矩形選區(qū)
看到這個(gè)詞,好像是說(shuō)圖像處理工具,其實(shí)非也,不管是VS還是EditPlus,其實(shí)都是支持矩形選區(qū)的。這對(duì)處理一些形如:去掉文章前端行號(hào)的情況有特效,矩形全區(qū)的選取方式就是按住Alt鍵,然后用鼠標(biāo)劃矩形選區(qū)(如圖7所示)。需要注意到是在”自動(dòng)換行”的情況下,是不能使用”矩形選區(qū)”的。你可以使用Ctrl+Shift +W來(lái)切換”自動(dòng)換行”或者”不自動(dòng)換行”視圖。

圖7:在EditPlus中選取矩形選區(qū)(注意紅色框內(nèi)的”自動(dòng)換行圖標(biāo)”)
提高工作效率,EditPlus 快捷鍵的使用
如果一個(gè)來(lái)你們公司面試程序員,連Ctrl + C 和Ctrl + V 都不用,而是使用”選中文本”→ 鼠標(biāo)右鍵 → 【復(fù)制】,然后再鼠標(biāo)右鍵→ 【粘貼】。你會(huì)不會(huì)錄用他呢?(你還別笑,以前我們公司還真面試過(guò)一個(gè)這樣的,所謂的”精通asp.net”的程序員)。所以熟練的使用軟件的快捷鍵,不僅僅能夠極大的提高工作效率,也從一個(gè)側(cè)面表現(xiàn)出一個(gè)人對(duì)此軟件的使用能力。EditPlus同樣也有很多的快捷鍵,下面是一些我經(jīng)常使用的EditPlus特有的快捷鍵(Ctrl +C 、Ctrl+H這樣的通用快捷鍵就不介紹了),略舉一二,更多的請(qǐng)參看文章《EditPlus快捷鍵》
以瀏覽器模式預(yù)覽文件 |
Ctrl + B |
開(kāi)始編輯”以瀏覽器模式預(yù)覽的文件” |
Ctrl + E |
新建html文件 |
Ctrl+Shift+N |
新建瀏覽器窗口(類(lèi)似于在EditPlus中打開(kāi)ie) |
Ctrl+Shift+B |
選中的字母切換為小寫(xiě) |
Ctrl+L |
選中的字母切換為大寫(xiě) |
Ctrl+U |
選中的詞組首字母大寫(xiě) |
Ctrl+Shift+U |
復(fù)制選定文本并追加到剪貼板中 |
Ctrl+Shift+C |
剪切選定文本并追加到剪貼板中 |
Ctrl+Shift+X |
創(chuàng)建當(dāng)前行的副本 |
Ctrl+J |
復(fù)制上一行的一個(gè)字符到當(dāng)前行 |
Ctrl+- |
剪切選定文本并追加到剪貼板中 |
Ctrl+Shift+X |
合并選定行 |
Ctrl+Shift+J |
反轉(zhuǎn)選定文本的大小寫(xiě) |
Ctrl+K |
開(kāi)始/結(jié)束選擇區(qū)域 |
Alt+Shift+B |
選擇當(dāng)前行 |
Ctrl+R |
全屏模式開(kāi)/關(guān) |
Ctrl+K |
顯示或隱藏標(biāo)尺 |
Alt+Shift+R |
顯示或隱藏制表符與空格 |
Alt+Shift+I |
顯示函數(shù)列表 |
Ctrl+F11 |
轉(zhuǎn)到當(dāng)前文檔的指定行 |
Ctrl + G |
設(shè)置或清除當(dāng)前行的標(biāo)記 |
F9 |
轉(zhuǎn)到下一個(gè)標(biāo)記位置 |
F4 |
轉(zhuǎn)到上一個(gè)標(biāo)記位置 |
Shift+F4 |
清除當(dāng)前文檔中的所有標(biāo)記 |
Ctrl+Shift+F9 |
搜索一對(duì)匹配的括號(hào) |
Ctrl+] |
搜索一對(duì)匹配的括號(hào)并選擇該文本 |
Ctrl+Shift+] |
切換當(dāng)前文檔的自動(dòng)換行功能 |
Ctrl+Shift+W |
編輯當(dāng)前 HTML 頁(yè)面的源文件 |
Ctrl+E |
常用的類(lèi)有BufferedReader,
Scanner。
實(shí)例程序:
一,利用 Scanner 實(shí)現(xiàn)從鍵盤(pán)讀入integer或float 型數(shù)據(jù)
import
java.util.*;
//import
java.io.*;
class Abc
{
public static
void main(String args[])
{
Scanner in=new
Scanner(System.in); //使用Scanner類(lèi)定義對(duì)象
System.out.println("please input a float number");
float
a=in.nextFloat(); //接收f(shuō)loat型數(shù)據(jù)
System.out.println(a);
System.out.println("please input a integer number");
int
b=in.nextInt(); //接收整形數(shù)據(jù)
System.out.println(b);
}
}
二,利用 BufferedReader實(shí)現(xiàn)從鍵盤(pán)讀入字符串并寫(xiě)進(jìn)文件abc.txt中
import java.io.*;
public class
Test1
{
public static
void main(String[] args) throws IOException
{
BufferedReader buf = new
BufferedReader (new
InputStreamReader(System.in));
BufferedWriter buff = new
BufferedWriter(new FileWriter("abc.txt"));
String str = buf.readLine();
while(!str.equals("exit"))
{
buff.write(str);
buff.newLine();
str = buf.readLine();
}
buf.close();
buff.close();
}
}
關(guān)于JDK1.5 Scanner類(lèi)的說(shuō)明
Scanner是SDK1.5新增的一個(gè)類(lèi),可是使用該類(lèi)創(chuàng)建一個(gè)對(duì)象.
Scanner reader=new Scanner(System.in);
然后reader對(duì)象調(diào)用下列方法(函數(shù)),讀取用戶在命令行輸入的各種數(shù)據(jù)類(lèi)型:
next.Byte(),nextDouble(),nextFloat,nextInt(),nextLine(),nextLong(),nextShot()
使用nextLine()方法輸入行中可能包含空格.如果讀取的是一個(gè)單詞,則可調(diào)用
.next()方法
Java語(yǔ)言細(xì)節(jié)
Java作為一門(mén)優(yōu)秀的面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言,正在被越來(lái)越多的人使用。本文試圖列出作者在實(shí)際開(kāi)發(fā)中碰到的一些Java語(yǔ)言的容易被人忽視的細(xì)節(jié),希望能給正在學(xué)習(xí)Java語(yǔ)言的人有所幫助。
1,位移運(yùn)算越界怎么處理
考察下面的代碼輸出結(jié)果是多少?
int a=5;
System.out.println(a<<33);
按照常理推測(cè),把a(bǔ)左移33位應(yīng)該將a的所有有效位都移出去了,那剩下的都是零啊,所以輸出結(jié)果應(yīng)該是0才對(duì)啊,可是執(zhí)行后發(fā)現(xiàn)輸出結(jié)果是10,為什么
呢?因?yàn)镴ava語(yǔ)言對(duì)位移運(yùn)算作了優(yōu)化處理,Java語(yǔ)言對(duì)a<<b轉(zhuǎn)化為a<<(b%32)來(lái)處理,所以當(dāng)要移位的位數(shù)b超
過(guò)32時(shí),實(shí)際上移位的位數(shù)是b%32的值,那么上面的代碼中a<<33相當(dāng)于a<<1,所以輸出結(jié)果是10。
2,可以讓i!=i嗎?
當(dāng)你看到這個(gè)命題的時(shí)候一定會(huì)以為我瘋了,或者Java語(yǔ)言瘋了。這看起來(lái)是絕對(duì)不可能的,一個(gè)數(shù)怎么可能不等于它自己呢?或許就真的是Java語(yǔ)言瘋了,不信看下面的代碼輸出什么?
double i=0.0/0.0;
if(i==i){
System.out.println("Yes i==i");
}else{
System.out.println("No i!=i");
}
上面的代碼輸出"No i!=i",為什么會(huì)這樣呢?關(guān)鍵在0.0/0.0這個(gè)值,在IEEE
754浮點(diǎn)算術(shù)規(guī)則里保留了一個(gè)特殊的值用來(lái)表示一個(gè)不是數(shù)字的數(shù)量。這個(gè)值就是NaN("Not a
Number"的縮寫(xiě)),對(duì)于所有沒(méi)有良好定義的浮點(diǎn)計(jì)算都將得到這個(gè)值,比如:0.0/0.0;其實(shí)我們還可以直接使用Double.NaN來(lái)得到這個(gè)
值。在IEEE 754規(guī)范里面規(guī)定NaN不等于任何值,包括它自己。所以就有了i!=i的代碼。
3,怎樣的equals才安全?
我們都知道在Java規(guī)范里定義了equals方法覆蓋的5大原則:reflexive(反身性),symmetric(對(duì)稱(chēng)性),transitive(傳遞性),consistent(一致性),non-null(非空性)。那么考察下面的代碼:
public class Student{
private String name;
private int age;
public Student(String name,int age){
this.name=name;
this.age=age;
}
public boolean equals(Object obj){
if(obj instanceof Student){
Student s=(Student)obj;
if(s.name.equals(this.name) && s.age==this.age){
return true;
}
}
return super.equals(obj);
}
}
你認(rèn)為上面的代碼equals方法的覆蓋安全嗎?表面看起來(lái)好像沒(méi)什么問(wèn)題,這樣寫(xiě)也確實(shí)滿足了以上的五大原則。但其實(shí)這樣的覆蓋并不很安全,假如
Student類(lèi)還有一個(gè)子類(lèi)CollegeStudent,如果我拿一個(gè)Student對(duì)象和一個(gè)CollegeStudent對(duì)象equals,只要
這兩個(gè)對(duì)象有相同的name和age,它們就會(huì)被認(rèn)為相等,但實(shí)際上它們是兩個(gè)不同類(lèi)型的對(duì)象啊。問(wèn)題就出在instanceof這個(gè)運(yùn)算符上,因?yàn)檫@個(gè)
運(yùn)算符是向下兼容的,也就是說(shuō)一個(gè)CollegeStudent對(duì)象也被認(rèn)為是一個(gè)Student的實(shí)例。怎樣去解決這個(gè)問(wèn)題呢?那就只有不用
instanceof運(yùn)算符,而使用對(duì)象的getClass()方法來(lái)判斷兩個(gè)對(duì)象是否屬于同一種類(lèi)型,例如,將上面的equals()方法修改為:
public boolean equals(Object obj){
if(obj.getClass()==Student.class){
Student s=(Student)obj;
if(s.name.equals(this.name) && s.age==this.age){
return true;
}
}
return super.equals(obj);
}
這樣才能保證obj對(duì)象一定是Student的實(shí)例,而不會(huì)是Student的任何子類(lèi)的實(shí)例。
4,淺復(fù)制與深復(fù)制
1)淺復(fù)制與深復(fù)制概念
⑴淺復(fù)制(淺克隆)
被復(fù)制對(duì)象的所有變量都含有與原來(lái)的對(duì)象相同的值,而所有的對(duì)其他對(duì)象的引用仍然指向原來(lái)的對(duì)象。換言之,淺復(fù)制僅僅復(fù)制所考慮的對(duì)象,而不復(fù)制它所引用的對(duì)象。
⑵深復(fù)制(深克隆)
被復(fù)制對(duì)象的所有變量都含有與原來(lái)的對(duì)象相同的值,除去那些引用其他對(duì)象的變量。那些引用其他對(duì)象的變量將指向被復(fù)制過(guò)的新對(duì)象,而不再是原有的那些被引用的對(duì)象。換言之,深復(fù)制把要復(fù)制的對(duì)象所引用的對(duì)象都復(fù)制了一遍。
2)Java的clone()方法
⑴clone方法將對(duì)象復(fù)制了一份并返回給調(diào)用者。一般而言,clone()方法滿足:
①對(duì)任何的對(duì)象x,都有x.clone() !=x//克隆對(duì)象與原對(duì)象不是同一個(gè)對(duì)象
②對(duì)任何的對(duì)象x,都有x.clone().getClass()= =x.getClass()//克隆對(duì)象與原對(duì)象的類(lèi)型一樣
③如果對(duì)象x的equals()方法定義恰當(dāng),那么x.clone().equals(x)應(yīng)該成立。
⑵Java中對(duì)象的克隆
①為了獲取對(duì)象的一份拷貝,我們可以利用Object類(lèi)的clone()方法。
②在派生類(lèi)中覆蓋基類(lèi)的clone()方法,并聲明為public。
③在派生類(lèi)的clone()方法中,調(diào)用super.clone()。
④在派生類(lèi)中實(shí)現(xiàn)Cloneable接口。
請(qǐng)看如下代碼:
class Student implements Cloneable{
String name;
int age;
Student(String name,int age){
this.name=name;
this.age=age;
}
public Object clone(){
Object obj=null;
try{
obj=(Student)super.clone();
//Object中的clone()識(shí)別出你要復(fù)制的是哪一個(gè)對(duì)象。
}
catch(CloneNotSupportedException e){
e.printStackTrace();
}
return obj;
}
}
public static void main(String[] args){
Student s1=new Student("zhangsan",18);
Student s2=(Student)s1.clone();
s2.name="lisi";
s2.age=20;
System.out.println("name="+s1.name+","+"age="+s1.age);//修改學(xué)生2
//后,不影響學(xué)生1的值。
}
說(shuō)明:
①為什么我們?cè)谂缮?lèi)中覆蓋Object的clone()方法時(shí),一定要調(diào)用super.clone()呢?在運(yùn)行時(shí)刻,Object中的clone()
識(shí)別出你要復(fù)制的是哪一個(gè)對(duì)象,然后為此對(duì)象分配空間,并進(jìn)行對(duì)象的復(fù)制,將原始對(duì)象的內(nèi)容一一復(fù)制到新對(duì)象的存儲(chǔ)空間中。
②繼承自java.lang.Object類(lèi)的clone()方法是淺復(fù)制。以下代碼可以證明之。
class Teacher{
String name;
int age;
Teacher(String name,int age){
this.name=name;
this.age=age;
}
}
class Student implements Cloneable{
String name;
int age;
Teacher t;//學(xué)生1和學(xué)生2的引用值都是一樣的。
Student(String name,int age,Teacher t){
this.name=name;
this.age=age;
this.t=t;
}
public Object clone(){
Student stu=null;
try{
stu=(Student)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
stu.t=(Teacher)t.clone();
return stu;
}
public static void main(String[] args){
Teacher t=new Teacher("tangliang",30);
Student s1=new Student("zhangsan",18,t);
Student s2=(Student)s1.clone();
s2.t.name="tony";
s2.t.age=40;
System.out.println("name="+s1.t.name+","+"age="+s1.t.age);
//學(xué)生1的老師成為tony,age為40。
}
}
那應(yīng)該如何實(shí)現(xiàn)深層次的克隆,即修改s2的老師不會(huì)影響s1的老師?代碼改進(jìn)如下。
class Teacher implements Cloneable{
String name;
int age;
Teacher(String name,int age){
this.name=name;
this.age=age;
}
public Object clone(){
Object obj=null;
try{
obj=super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
return obj;
}
}
class Student implements Cloneable{
String name;
int age;
Teacher t;
Student(String name,int age,Teacher t){
this.name=name;
this.age=age;
this.t=t;
}
public Object clone(){
Student stu=null;
try{
stu=(Student)super.clone();
}catch(CloneNotSupportedException e){
e.printStackTrace();
}
stu.t=(Teacher)t.clone();
return stu;
}
}
public static void main(String[] args){
Teacher t=new Teacher("tangliang",30);
Student s1=new Student("zhangsan",18,t);
Student s2=(Student)s1.clone();
s2.t.name="tony";
s2.t.age=40;
System.out.println("name="+s1.t.name+","+"age="+s1.t.age);
//學(xué)生1的老師不改變。
}
3)利用串行化來(lái)做深復(fù)制
把對(duì)象寫(xiě)到流里的過(guò)程是串行化(Serilization)過(guò)程,Java程序員又非常形象地稱(chēng)為“冷凍”或者“腌咸菜(picking)”過(guò)程;而把對(duì)
象從流中讀出來(lái)的并行化(Deserialization)過(guò)程則叫做“解凍”或者“回鮮(depicking)”過(guò)程。應(yīng)當(dāng)指出的是,寫(xiě)在流里的是對(duì)象
的一個(gè)拷貝,而原對(duì)象仍然存在于JVM里面,因此“腌成咸菜”的只是對(duì)象的一個(gè)拷貝,Java咸菜還可以回鮮。
在Java語(yǔ)言里深復(fù)制一個(gè)對(duì)象,常??梢韵仁箤?duì)象實(shí)現(xiàn)Serializable接口,然后把對(duì)象(實(shí)際上只是對(duì)象的一個(gè)拷貝)寫(xiě)到一個(gè)流里(腌成咸菜),再?gòu)牧骼镒x出來(lái)(把咸菜回鮮),便可以重建對(duì)象。
如下為深復(fù)制源代碼。
public Object deepClone(){
//將對(duì)象寫(xiě)到流里
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//從流里讀出來(lái)
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
這樣做的前提是對(duì)象以及對(duì)象內(nèi)部所有引用到的對(duì)象都是可串行化的,否則,就需要仔細(xì)考察那些不可串行化的對(duì)象可否設(shè)成transient,從而將之排除在復(fù)制過(guò)程之外。上例代碼改進(jìn)如下。
class Teacher implements Serializable{
String name;
int age;
Teacher(String name,int age){
this.name=name;
this.age=age;
}
}
class Student implements Serializable
{
String name;//常量對(duì)象。
int age;
Teacher t;//學(xué)生1和學(xué)生2的引用值都是一樣的。
Student(String name,int age,Teacher t){
this.name=name;
this.age=age;
this.p=p;
}
public Object deepClone() throws IOException,
OptionalDataException,ClassNotFoundException
{
//將對(duì)象寫(xiě)到流里
ByteArrayOutoutStream bo=new ByteArrayOutputStream();
ObjectOutputStream oo=new ObjectOutputStream(bo);
oo.writeObject(this);
//從流里讀出來(lái)
ByteArrayInputStream bi=new ByteArrayInputStream(bo.toByteArray());
ObjectInputStream oi=new ObjectInputStream(bi);
return(oi.readObject());
}
}
public static void main(String[] args){
Teacher t=new Teacher("tangliang",30);
Student s1=new Student("zhangsan",18,t);
Student s2=(Student)s1.deepClone();
s2.t.name="tony";
s2.t.age=40;
System.out.println("name="+s1.t.name+","+"age="+s1.t.age);
//學(xué)生1的老師不改變。
}
5,String類(lèi)和對(duì)象池 我們知道得到String對(duì)象有兩種辦法: String str1="hello"; String str2=new
String("hello");
這兩種創(chuàng)建String對(duì)象的方法有什么差異嗎?當(dāng)然有差異,差異就在于第一種方法在對(duì)象池中拿對(duì)象,第二種方法直接生成新的對(duì)象。在JDK5.0里面,
Java虛擬機(jī)在啟動(dòng)的時(shí)候會(huì)實(shí)例化9個(gè)對(duì)象池,這9個(gè)對(duì)象池分別用來(lái)存儲(chǔ)8種基本類(lèi)型的包裝類(lèi)對(duì)象和String對(duì)象。當(dāng)我們?cè)诔绦蛑兄苯佑秒p引號(hào)括起
來(lái)一個(gè)字符串時(shí),JVM就到String的對(duì)象池里面去找看是否有一個(gè)值相同的對(duì)象,如果有,就拿現(xiàn)成的對(duì)象,如果沒(méi)有就在對(duì)象池里面創(chuàng)建一個(gè)對(duì)象,并返
回。所以我們發(fā)現(xiàn)下面的代碼輸出true: String str1="hello"; String str2="hello";
System.out.println(str1==str2);
這說(shuō)明str1和str2指向同一個(gè)對(duì)象,因?yàn)樗鼈兌际窃趯?duì)象池中拿到的,而下面的代碼輸出為false: String str3="hello"
String str4=new String("hello"); System.out.println(str3==str4);
因?yàn)樵谌魏吻闆r下,只要你去new一個(gè)String對(duì)象那都是創(chuàng)建了新的對(duì)象。
與此類(lèi)似的,在JDK5.0里面8種基本類(lèi)型的包裝類(lèi)也有這樣的差異: Integer i1=5;//在對(duì)象池中拿 Integer i2
=5;//所以i1==i2 Integer i3=new Integer(5);//重新創(chuàng)建新對(duì)象,所以i2!=i3
對(duì)象池的存在是為了避免頻繁的創(chuàng)建和銷(xiāo)毀對(duì)象而影響系統(tǒng)性能,那我們自己寫(xiě)的類(lèi)是否也可以使用對(duì)象池呢?當(dāng)然可以,考察以下代碼: class
Student{ private String name; private int age; private static HashSet pool=new HashSet();//對(duì)象池
public Student(String name,int age){
this.name=name;
this.age=age;
}
//使用對(duì)象池來(lái)得到對(duì)象的方法
public static Student newInstance(String name,int age){
//循環(huán)遍歷對(duì)象池
for(Student stu:pool){
if(stu.name.equals(name) && stu.age==age){
return stu;
}
}
//如果找不到值相同的Student對(duì)象,則創(chuàng)建一個(gè)Student對(duì)象
//并把它加到對(duì)象池中然后返回該對(duì)象。
Student stu=new Student(name,age);
pool.add(stu);
return stu;
}
}
public class Test{
public static void main(String[] args){
Student stu1=Student.newInstance("tangliang",30);//對(duì)象池中拿
Student stu2=Student.newInstance("tangliang",30);//所以stu1==stu2
Student stu3=new Student("tangliang",30);//重新創(chuàng)建,所以stu1!=stu3
System.out.println(stu1==stu2);
System.out.println(stu1==stu3);
}
}
6,2.0-1.1==0.9嗎? 考察下面的代碼: double a=2.0,b=1.1,c=0.9; if(a-b==c){
System.out.println("YES!"); }else{ System.out.println("NO!"); }
以上代碼輸出的結(jié)果是多少呢?你認(rèn)為是“YES!”嗎?那么,很遺憾的告訴你,不對(duì),Java語(yǔ)言再一次cheat了你,以上代碼會(huì)輸出“NO!”。為什
么會(huì)這樣呢?其實(shí)這是由實(shí)型數(shù)據(jù)的存儲(chǔ)方式?jīng)Q定的。我們知道實(shí)型數(shù)據(jù)在內(nèi)存空間中是近似存儲(chǔ)的,所以2.0-1.1的結(jié)果不是0.9,而是
0.88888888889。所以在做實(shí)型數(shù)據(jù)是否相等的判斷時(shí)要非常的謹(jǐn)慎。一般來(lái)說(shuō),我們不建議在代碼中直接判斷兩個(gè)實(shí)型數(shù)據(jù)是否相等,如果一定要比
較是否相等的話我們也采用以下方式來(lái)判斷: if(Math.abs(a-b)<1e-5){ //相等 }else{ //不相等 }
上面的代碼判斷a與b之差的絕對(duì)值是否小于一個(gè)足夠小的數(shù)字,如果是,則認(rèn)為a與b相等,否則,不相等。
7,判斷奇數(shù) 以下的方法判斷某個(gè)整數(shù)是否是奇數(shù),考察是否正確: public boolean isOdd(int n){ return
(n%2==1); }
很多人認(rèn)為上面的代碼沒(méi)問(wèn)題,但實(shí)際上這段代碼隱藏著一個(gè)非常大的BUG,當(dāng)n的值是正整數(shù)時(shí),以上的代碼能夠得到正確結(jié)果,但當(dāng)n的值是負(fù)整數(shù)時(shí),以上
方法不能做出正確判斷。例如,當(dāng)n=-3時(shí),以上方法返回false。因?yàn)楦鶕?jù)Java語(yǔ)言規(guī)范的定義,Java語(yǔ)言里的求余運(yùn)算符(%)得到的結(jié)果與運(yùn)
算符左邊的值符號(hào)相同,所以,-3%2的結(jié)果是-1,而不是1。那么上面的方法正確的寫(xiě)法應(yīng)該是: public boolean isOdd(int
n){ return (n%2!=0); }
8,拓寬數(shù)值類(lèi)型會(huì)造成精度丟失嗎?
Java語(yǔ)言的8種基本數(shù)據(jù)類(lèi)型中7種都可以看作是數(shù)值類(lèi)型,我們知道對(duì)于數(shù)值類(lèi)型的轉(zhuǎn)換有一個(gè)規(guī)律:從窄范圍轉(zhuǎn)化成寬范圍能夠自動(dòng)類(lèi)型轉(zhuǎn)換,反之則必須
強(qiáng)制轉(zhuǎn)換。請(qǐng)看下圖:
byte-->short-->int-->long-->float-->double
char-->int
我們把順箭頭方向的轉(zhuǎn)化叫做拓寬類(lèi)型,逆箭頭方向的轉(zhuǎn)化叫做窄化類(lèi)型。一般我們認(rèn)為因?yàn)轫樇^方向的轉(zhuǎn)化不會(huì)有數(shù)據(jù)和精度的丟失,所以Java語(yǔ)言允許自
動(dòng)轉(zhuǎn)化,而逆箭頭方向的轉(zhuǎn)化可能會(huì)造成數(shù)據(jù)和精度的丟失,所以Java語(yǔ)言要求程序員在程序中明確這種轉(zhuǎn)化,也就是強(qiáng)制轉(zhuǎn)換。那么拓寬類(lèi)型就一定不會(huì)造成
數(shù)據(jù)和精度丟失嗎?請(qǐng)看下面代碼:
int i=2000000000;
int num=0;
for(float f=i;f
9,i=i+1和i+=1完全等價(jià)嗎?
可能有很多程序員認(rèn)為i+=1只是i=i+1的簡(jiǎn)寫(xiě)方式,其實(shí)不然,它們一個(gè)使用簡(jiǎn)單賦值運(yùn)算,一個(gè)使用復(fù)合賦值運(yùn)算,而簡(jiǎn)單賦值運(yùn)算和復(fù)合賦值運(yùn)算的最
大差別就在于:復(fù)合賦值運(yùn)算符會(huì)自動(dòng)地將運(yùn)算結(jié)果轉(zhuǎn)型為其左操作數(shù)的類(lèi)型??纯匆韵碌膬煞N寫(xiě)法,你就知道它們的差別在哪兒了:
(1) byte i=5;
i+=1;
(2) byte i=5;
i=i+1;
第一種寫(xiě)法編譯沒(méi)問(wèn)題,而第二種寫(xiě)法卻編譯通不過(guò)。原因就在于,當(dāng)使用復(fù)合賦值運(yùn)算符進(jìn)行操作時(shí),即使右邊算出的結(jié)果是int類(lèi)型,系統(tǒng)也會(huì)將其值轉(zhuǎn)化為
左邊的byte類(lèi)型,而使用簡(jiǎn)單賦值運(yùn)算時(shí)沒(méi)有這樣的優(yōu)待,系統(tǒng)會(huì)認(rèn)為將i+1的值賦給i是將int類(lèi)型賦給byte,所以要求強(qiáng)制轉(zhuǎn)換。理解了這一點(diǎn)
后,我們?cè)賮?lái)看一個(gè)例子:
byte b=120;
b+=20;
System.out.println("b="+b);
說(shuō)到這里你應(yīng)該明白了,上例中輸出b的值不是140,而是-116。因?yàn)?20+20的值已經(jīng)超出了一個(gè)byte表示的范圍,而當(dāng)我們使用復(fù)合賦值運(yùn)
算時(shí)系統(tǒng)會(huì)自動(dòng)作類(lèi)型的轉(zhuǎn)化,將140強(qiáng)轉(zhuǎn)成byte,所以得到是-116。由此可見(jiàn),在使用復(fù)合賦值運(yùn)算符時(shí)還得小心,因?yàn)檫@種類(lèi)型轉(zhuǎn)換是在不知不覺(jué)中
進(jìn)行的,所以得到的結(jié)果就有可能和你的預(yù)想不一樣。
下面引用由zhangyue在 2007/09/01 09:07pm 發(fā)表的內(nèi)容:
唐老師:
long類(lèi)型為什么能自動(dòng)轉(zhuǎn)換成float類(lèi)型啊!!
long是64bits;
float是32bits;
...
long
能自動(dòng)轉(zhuǎn)換為float,但這種轉(zhuǎn)換會(huì)造成精度的丟失,float中只保留了原來(lái)long類(lèi)型的低24位的數(shù)據(jù)。這是Java語(yǔ)言中三種基本類(lèi)型的自動(dòng)轉(zhuǎn)
換會(huì)造成精度丟失的情況之一,另兩種情況是int-->float 和long-->double,詳情請(qǐng)參考:
8,拓寬數(shù)值類(lèi)型會(huì)造成精度丟失嗎?
下面引用由plastrio在 2007/09/01 09:08am 發(fā)表的內(nèi)容:
請(qǐng)教個(gè)問(wèn)題 您給我們0703講線程時(shí) 有段課堂代碼為什么 用synchronized(Object obj) 鎖代碼塊而不是一般教程上講的 synchronized(this)
當(dāng)兩個(gè)線程在運(yùn)行時(shí)this不代表同一個(gè)對(duì)象時(shí)就不能用synchronized(this)來(lái)鎖。必須定義一個(gè)唯一的公共對(duì)象來(lái)聲明,如:synchronized(obj)
例如:
public class ThreadTest{
public static void main(String[] args){
Thread t1=new Thread(new ThreadA());
Thread t2=new Thread(new ThreadA());//兩個(gè)線程對(duì)應(yīng)兩個(gè)不同的ThreadA對(duì)象。
t1.start();
t2.start();
}
}
class ThreadA implements Runnable{
static int i=0;
static Object obj=new Object();
public run(){
while(i<20){
synchronized(obj){//兩個(gè)線程在執(zhí)行時(shí)所引用的this不是同一個(gè)對(duì)象,所以寫(xiě)this就不能達(dá)到鎖的目的。
System.out.println(i);
i++;
}
}
}
}
在java語(yǔ)言中,位移操作共分三種,左位移(<<),右位移(>>)和無(wú)符號(hào)右位移(>>>)。如果將位移
運(yùn)算表示為公式的話,即n operator
s。其中,operator表示上述的三種位移操作之一;n和s表示操作數(shù),必須是可以轉(zhuǎn)化成int類(lèi)型的,否則出現(xiàn)運(yùn)行時(shí)錯(cuò)誤。n是原始數(shù)值,s表示位
移距離。該公式的含義是n按照operator運(yùn)算符含義位移s位。位移的距離使用掩碼32(類(lèi)似于子網(wǎng)掩碼),即位移距離總是在0~31之間,超出這個(gè)
范圍的位移距離(包括負(fù)數(shù))會(huì)被轉(zhuǎn)化在這個(gè)范圍里。也就是說(shuō)真正的位移距離是n%32,所以唐老師的位移距離33實(shí)際上是1。n<<s的結(jié)果
(無(wú)論是否溢出)總是等價(jià)于n與2的n%32次冪的乘積。在唐老師的例子里面,位移距離是33%32即1,2的1次冪是2,5與2的乘積是10.所以最終
結(jié)果是10。對(duì)于右位移操作n<<s的結(jié)果(無(wú)論是否溢出)總是等價(jià)于n與2的n%32次冪的商。(以上內(nèi)容參考java規(guī)范15.9)