posts - 22, comments - 32, trackbacks - 0, articles - 73
            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          2012年10月17日

          最近在公司有點(diǎn)時(shí)間所以深入研究了下數(shù)據(jù)庫(kù)索引btree/b+tree數(shù)據(jù)結(jié)構(gòu)和原理,由此牽引出了好多問(wèn)題,請(qǐng)看如下帶著問(wèn)題研究。

          1:為什么 btree/b+tree 數(shù)據(jù)結(jié)構(gòu)適合數(shù)據(jù)庫(kù)索引,它到底是怎么樣一個(gè)原理和結(jié)構(gòu)?

          btree/b+tree 數(shù)據(jù)結(jié)構(gòu):

          在之前的文章中我們介紹過(guò)AVL樹(shù),紅黑樹(shù),它們都屬于二叉樹(shù),即每個(gè)節(jié)點(diǎn)最多只能擁有2個(gè)子節(jié)點(diǎn),而B(niǎo)-tree(B樹(shù))的每個(gè)節(jié)點(diǎn)可以擁有2個(gè)以上的子節(jié)點(diǎn),所以我們簡(jiǎn)單概括一下:B-tree就是一顆多路平衡查找樹(shù),它廣泛應(yīng)用于數(shù)據(jù)庫(kù)索引和文件系統(tǒng)中。

          首先我們介紹一下一顆 m 階B-tree的特性,那么這個(gè) m 階是怎么定義的呢?這里我們以一個(gè)節(jié)點(diǎn)能擁有的最大子節(jié)點(diǎn)數(shù)來(lái)表示這顆樹(shù)的階數(shù)。舉個(gè)例子,如果一個(gè)節(jié)點(diǎn)最多有 n 個(gè)key,那么這個(gè)節(jié)點(diǎn)最多就會(huì)有 n+1 個(gè)子節(jié)點(diǎn),這棵樹(shù)就叫做 n+1(m=n+1)階樹(shù)。一顆 m 階B-tree包括以下5條特性:

          1. 每個(gè)節(jié)點(diǎn)最多有 m 個(gè)子節(jié)點(diǎn)
          2. 除根節(jié)點(diǎn)和葉子節(jié)點(diǎn),其它每個(gè)節(jié)點(diǎn)至少有 [m/2] (向上取整的意思)個(gè)子節(jié)點(diǎn)
          3. 若根節(jié)點(diǎn)不是葉子節(jié)點(diǎn),則其至少有2個(gè)子節(jié)點(diǎn)
          4. 所有NULL節(jié)點(diǎn)到根節(jié)點(diǎn)的高度都一樣
          5. 除根節(jié)點(diǎn)外,其它節(jié)點(diǎn)都包含 n 個(gè)key,其中 [m/2] -1 <= n <= m-1

          這些特性可能看著不太好理解,下面我們會(huì)介紹B-tree的插入,在插入節(jié)點(diǎn)的過(guò)程中我們就會(huì)慢慢理解這些特性了。B-tree的插入比較簡(jiǎn)單,就是一個(gè)節(jié)點(diǎn)至下而上的分裂過(guò)程。下面我們具體以一顆4階樹(shù)來(lái)展示B-tree的插入過(guò)程。

          首先我們 插入 200,300,400,沒(méi)有什么問(wèn)題,直接插入就好。

          | 200 | 300 | 400 |

          現(xiàn)在我們接著插入500,這個(gè)時(shí)候我們發(fā)現(xiàn)有點(diǎn)問(wèn)題,根據(jù)定義及特性1我們知道一顆4階B-tree的每個(gè)節(jié)點(diǎn)最多只能有3個(gè)key,插入500后這個(gè)節(jié)點(diǎn)就有4個(gè)key了。

          | 200 | 300 | 400 | 500 |

          這個(gè)時(shí)候我們就需要分裂,將中間的key上移到父節(jié)點(diǎn),左邊的作為左節(jié)點(diǎn),右邊的作為右節(jié)點(diǎn),如下圖所示:

          這個(gè)時(shí)候我們是不是就明白特性3了,如果根節(jié)點(diǎn)不是葉子節(jié)點(diǎn),那么它肯定發(fā)生了分裂,所以至少會(huì)有2個(gè)子節(jié)點(diǎn)。同樣我們接著插入600,700,800,900插入過(guò)程如下圖所示:

          現(xiàn)在根節(jié)點(diǎn)也已經(jīng)滿了,如果我們繼續(xù)插入910,920,會(huì)怎樣呢?根節(jié)點(diǎn)就會(huì)繼續(xù)分裂,樹(shù)繼續(xù)向上生長(zhǎng)。看下圖:

          通過(guò)整個(gè)的插入過(guò)程我們也會(huì)發(fā)現(xiàn),B-tree和二叉樹(shù)的一個(gè)顯著的區(qū)別就是,B-tree是從下往上生長(zhǎng),而二叉樹(shù)是從上往下生長(zhǎng)的。現(xiàn)在我們想想特性2和特性5是為什么?首先我們知道子節(jié)點(diǎn)的個(gè)數(shù)是等于key的數(shù)目+1,然后一個(gè)節(jié)點(diǎn)達(dá)到m個(gè)key后就會(huì)分裂,所以分裂后的節(jié)點(diǎn)最少能得到 m/2 - 1個(gè)key 。為啥還要減一呢?因?yàn)檫€要拿一個(gè)作為父節(jié)點(diǎn)。所以這個(gè)節(jié)點(diǎn)最少回?fù)碛?m/2 - 1 + 1 = m/2 個(gè)子節(jié)點(diǎn)。同樣得到特性5,因?yàn)樽钌儆衜/2個(gè)子節(jié)點(diǎn),所以最少就含有m/2-1個(gè)key,m 階樹(shù),每個(gè)節(jié)點(diǎn)存到了m個(gè)key就會(huì)分裂,所以最多就有 m-1個(gè)key。

          根據(jù)以上特性我們能推出一棵含有N個(gè)總關(guān)鍵字?jǐn)?shù)的m階的B-tree樹(shù)的最大高度h的值,

          樹(shù)的高度h: 1, 2, 3 , 4 ,.......... , h

          節(jié)點(diǎn)個(gè)數(shù)s: 1, 2, 2*(m/2), 2*(m/2)(m/2), ........ ,2*(m/2)的h-2次方

          s = 1 + 2(1 - (m/2)^{h-1} )/(1- (m/2))

          N = 1 + s * ((m/2) - 1) = 2 * ((m/2)^{h-1} ) - 1

          h = log┌m/2┐((N+1)/2 )+1

          2:為什么btree/b+tree 為常用數(shù)據(jù)庫(kù)索引結(jié)構(gòu)?

          上文說(shuō)過(guò),紅黑樹(shù)等數(shù)據(jù)結(jié)構(gòu)也可以用來(lái)實(shí)現(xiàn)索引,但是文件系統(tǒng)及數(shù)據(jù)庫(kù)系統(tǒng)普遍采用B-/+Tree作為索引結(jié)構(gòu),這一節(jié)將結(jié)合計(jì)算機(jī)組成原理相關(guān)知識(shí)討論B-/+Tree作為索引的理論基礎(chǔ)。

          一般來(lái)說(shuō),索引本身也很大,不可能全部存儲(chǔ)在內(nèi)存中,因此索引往往以索引文件的形式存儲(chǔ)的磁盤上。這樣的話,索引查找過(guò)程中就要產(chǎn)生磁盤I/O消耗,相對(duì)于內(nèi)存存取,I/O存取的消耗要高幾個(gè)數(shù)量級(jí),所以評(píng)價(jià)一個(gè)數(shù)據(jù)結(jié)構(gòu)作為索引的優(yōu)劣最重要的指標(biāo)就是在查找過(guò)程中磁盤I/O操作次數(shù)的漸進(jìn)復(fù)雜度。換句話說(shuō),索引的結(jié)構(gòu)組織要盡量減少查找過(guò)程中磁盤I/O的存取次數(shù)。下面先介紹內(nèi)存和磁盤存取原理,然后再結(jié)合這些原理分析B-/+Tree作為索引的效率。

          主存存取原理

          目前計(jì)算機(jī)使用的主存基本都是隨機(jī)讀寫存儲(chǔ)器(RAM),現(xiàn)代RAM的結(jié)構(gòu)和存取原理比較復(fù)雜,這里本文拋卻具體差別,抽象出一個(gè)十分簡(jiǎn)單的存取模型來(lái)說(shuō)明RAM的工作原理。

          圖5

          從抽象角度看,主存是一系列的存儲(chǔ)單元組成的矩陣,每個(gè)存儲(chǔ)單元存儲(chǔ)固定大小的數(shù)據(jù)。每個(gè)存儲(chǔ)單元有唯一的地址,現(xiàn)代主存的編址規(guī)則比較復(fù)雜,這里將其簡(jiǎn)化成一個(gè)二維地址:通過(guò)一個(gè)行地址和一個(gè)列地址可以唯一定位到一個(gè)存儲(chǔ)單元。圖5展示了一個(gè)4 x 4的主存模型。

          主存的存取過(guò)程如下:

          當(dāng)系統(tǒng)需要讀取主存時(shí),則將地址信號(hào)放到地址總線上傳給主存,主存讀到地址信號(hào)后,解析信號(hào)并定位到指定存儲(chǔ)單元,然后將此存儲(chǔ)單元數(shù)據(jù)放到數(shù)據(jù)總線上,供其它部件讀取。

          寫主存的過(guò)程類似,系統(tǒng)將要寫入單元地址和數(shù)據(jù)分別放在地址總線和數(shù)據(jù)總線上,主存讀取兩個(gè)總線的內(nèi)容,做相應(yīng)的寫操作。

          這里可以看出,主存存取的時(shí)間僅與存取次數(shù)呈線性關(guān)系,因?yàn)椴淮嬖跈C(jī)械操作,兩次存取的數(shù)據(jù)的“距離”不會(huì)對(duì)時(shí)間有任何影響,例如,先取A0再取A1和先取A0再取D3的時(shí)間消耗是一樣的。

          磁盤存取原理

          上文說(shuō)過(guò),索引一般以文件形式存儲(chǔ)在磁盤上,索引檢索需要磁盤I/O操作。與主存不同,磁盤I/O存在機(jī)械運(yùn)動(dòng)耗費(fèi),因此磁盤I/O的時(shí)間消耗是巨大的。

          圖6是磁盤的整體結(jié)構(gòu)示意圖。

          圖6

          一個(gè)磁盤由大小相同且同軸的圓形盤片組成,磁盤可以轉(zhuǎn)動(dòng)(各個(gè)磁盤必須同步轉(zhuǎn)動(dòng))。在磁盤的一側(cè)有磁頭支架,磁頭支架固定了一組磁頭,每個(gè)磁頭負(fù)責(zé)存取一個(gè)磁盤的內(nèi)容。磁頭不能轉(zhuǎn)動(dòng),但是可以沿磁盤半徑方向運(yùn)動(dòng)(實(shí)際是斜切向運(yùn)動(dòng)),每個(gè)磁頭同一時(shí)刻也必須是同軸的,即從正上方向下看,所有磁頭任何時(shí)候都是重疊的(不過(guò)目前已經(jīng)有多磁頭獨(dú)立技術(shù),可不受此限制)。

          圖7是磁盤結(jié)構(gòu)的示意圖。

          圖7

          盤片被劃分成一系列同心環(huán),圓心是盤片中心,每個(gè)同心環(huán)叫做一個(gè)磁道,所有半徑相同的磁道組成一個(gè)柱面。磁道被沿半徑線劃分成一個(gè)個(gè)小的段,每個(gè)段叫做一個(gè)扇區(qū),每個(gè)扇區(qū)是磁盤的最小存儲(chǔ)單元。為了簡(jiǎn)單起見(jiàn),我們下面假設(shè)磁盤只有一個(gè)盤片和一個(gè)磁頭。

          當(dāng)需要從磁盤讀取數(shù)據(jù)時(shí),系統(tǒng)會(huì)將數(shù)據(jù)邏輯地址傳給磁盤,磁盤的控制電路按照尋址邏輯將邏輯地址翻譯成物理地址,即確定要讀的數(shù)據(jù)在哪個(gè)磁道,哪個(gè)扇區(qū)。為了讀取這個(gè)扇區(qū)的數(shù)據(jù),需要將磁頭放到這個(gè)扇區(qū)上方,為了實(shí)現(xiàn)這一點(diǎn),磁頭需要移動(dòng)對(duì)準(zhǔn)相應(yīng)磁道,這個(gè)過(guò)程叫做尋道,所耗費(fèi)時(shí)間叫做尋道時(shí)間,然后磁盤旋轉(zhuǎn)將目標(biāo)扇區(qū)旋轉(zhuǎn)到磁頭下,這個(gè)過(guò)程耗費(fèi)的時(shí)間叫做旋轉(zhuǎn)時(shí)間。

          局部性原理與磁盤預(yù)讀

          由于存儲(chǔ)介質(zhì)的特性,磁盤本身存取就比主存慢很多,再加上機(jī)械運(yùn)動(dòng)耗費(fèi),磁盤的存取速度往往是主存的幾百分分之一,因此為了提高效率,要盡量減少磁盤I/O。為了達(dá)到這個(gè)目的,磁盤往往不是嚴(yán)格按需讀取,而是每次都會(huì)預(yù)讀,即使只需要一個(gè)字節(jié),磁盤也會(huì)從這個(gè)位置開(kāi)始,順序向后讀取一定長(zhǎng)度的數(shù)據(jù)放入內(nèi)存。這樣做的理論依據(jù)是計(jì)算機(jī)科學(xué)中著名的局部性原理:

          當(dāng)一個(gè)數(shù)據(jù)被用到時(shí),其附近的數(shù)據(jù)也通常會(huì)馬上被使用。

          程序運(yùn)行期間所需要的數(shù)據(jù)通常比較集中。

          由于磁盤順序讀取的效率很高(不需要尋道時(shí)間,只需很少的旋轉(zhuǎn)時(shí)間),因此對(duì)于具有局部性的程序來(lái)說(shuō),預(yù)讀可以提高I/O效率。

          預(yù)讀的長(zhǎng)度一般為頁(yè)(page)的整倍數(shù)。頁(yè)是計(jì)算機(jī)管理存儲(chǔ)器的邏輯塊,硬件及操作系統(tǒng)往往將主存和磁盤存儲(chǔ)區(qū)分割為連續(xù)的大小相等的塊,每個(gè)存儲(chǔ)塊稱為一頁(yè)(在許多操作系統(tǒng)中,頁(yè)得大小通常為4k),主存和磁盤以頁(yè)為單位交換數(shù)據(jù)。當(dāng)程序要讀取的數(shù)據(jù)不在主存中時(shí),會(huì)觸發(fā)一個(gè)缺頁(yè)異常,此時(shí)系統(tǒng)會(huì)向磁盤發(fā)出讀盤信號(hào),磁盤會(huì)找到數(shù)據(jù)的起始位置并向后連續(xù)讀取一頁(yè)或幾頁(yè)載入內(nèi)存中,然后異常返回,程序繼續(xù)運(yùn)行。

          B-/+Tree索引的性能分析

          到這里終于可以分析B-/+Tree索引的性能了。

          上文說(shuō)過(guò)一般使用磁盤I/O次數(shù)評(píng)價(jià)索引結(jié)構(gòu)的優(yōu)劣。先從B-Tree分析,根據(jù)B-Tree的定義,可知檢索一次最多需要訪問(wèn)h個(gè)節(jié)點(diǎn)。數(shù)據(jù)庫(kù)系統(tǒng)的設(shè)計(jì)者巧妙利用了磁盤預(yù)讀原理,將一個(gè)節(jié)點(diǎn)的大小設(shè)為等于一個(gè)頁(yè),這樣每個(gè)節(jié)點(diǎn)只需要一次I/O就可以完全載入。為了達(dá)到這個(gè)目的,在實(shí)際實(shí)現(xiàn)B- Tree還需要使用如下技巧:

          每次新建節(jié)點(diǎn)時(shí),直接申請(qǐng)一個(gè)頁(yè)的空間,這樣就保證一個(gè)節(jié)點(diǎn)物理上也存儲(chǔ)在一個(gè)頁(yè)里,加之計(jì)算機(jī)存儲(chǔ)分配都是按頁(yè)對(duì)齊的,就實(shí)現(xiàn)了一個(gè)node只需一次I/O。

          B-Tree中一次檢索最多需要h-1次I/O(根節(jié)點(diǎn)常駐內(nèi)存),漸進(jìn)復(fù)雜度為O(h)=O(logdN)。一般實(shí)際應(yīng)用中,出度d是非常大的數(shù)字,通常超過(guò)100,因此h非常小(通常不超過(guò)3)。

          綜上所述,用B-Tree作為索引結(jié)構(gòu)效率是非常高的。

          而紅黑樹(shù)這種結(jié)構(gòu),h明顯要深的多。由于邏輯上很近的節(jié)點(diǎn)(父子)物理上可能很遠(yuǎn),無(wú)法利用局部性,所以紅黑樹(shù)的I/O漸進(jìn)復(fù)雜度也為O(h),效率明顯比B-Tree差很多。

          上文還說(shuō)過(guò),B+Tree更適合外存索引,原因和內(nèi)節(jié)點(diǎn)出度d有關(guān)。從上面分析可以看到,d越大索引的性能越好,而出度的上限取決于節(jié)點(diǎn)內(nèi)key和data的大小:

          dmax = floor(pagesize / (keysize + datasize + pointsize))  (pagesize – dmax >= pointsize)

          dmax = floor(pagesize / (keysize + datasize + pointsize)) - 1  (pagesize – dmax < pointsize)

          floor表示向下取整。由于B+Tree內(nèi)節(jié)點(diǎn)去掉了data域,因此可以擁有更大的出度,擁有更好的性能。

          這一章從理論角度討論了與索引相關(guān)的數(shù)據(jù)結(jié)構(gòu)與算法問(wèn)題,下一章將討論B+Tree是如何具體實(shí)現(xiàn)為MySQL中索引,同時(shí)將結(jié)合MyISAM和InnDB存儲(chǔ)引擎介紹非聚集索引和聚集索引兩種不同的索引實(shí)現(xiàn)形式。



          posted @ 2018-01-25 13:44 為自己代言 閱讀(3834) | 評(píng)論 (0)編輯 收藏

          同事遇到問(wèn)題是一個(gè)java web 工程,依賴了一個(gè)jar包,但是jar包中也有自己一套配置文件(例spring 配置文件,資源文件等),這樣如果讓web工程中的war 包去加載jar 中的配置文件和資源文件呢?
          我當(dāng)時(shí)也有一個(gè)思想誤區(qū),以為web中加載不到j(luò)ar中的文件,但是經(jīng)過(guò)一番研究,終于明白了,按J2EE規(guī)范中web-inf/lib目錄下是web工程的classpath 目錄,容器會(huì)自動(dòng)去掃描這個(gè)目錄下所有的配置文件和jar加載到容器中,即:像jar中有自己一套配置文件,war 中又要依賴jar包,這樣只需要把這些jar包打成war時(shí)候放到web-inf/lib下即可。
          注意:1:jar包和war包中配置文件和一些資源文件不能重名。
                  
                   2:要在war包中的spring 配置文件中引入jar包中的配置文件。



          posted @ 2015-04-24 20:42 為自己代言 閱讀(6937) | 評(píng)論 (0)編輯 收藏

          1、不同的tomcat的啟動(dòng)文件startup.sh 中要指定各自的CATALINA_HOME和CATALINA_BASE這兩個(gè)環(huán)境變量。

          2、不同的tomcat啟動(dòng)和關(guān)閉監(jiān)聽(tīng)不同的端口

          很多人喜歡把CATALINA_HOME和CATALINA_BASE配置到系統(tǒng)環(huán)境變量中去,我們不這么做,我們要做的只是把JDK及CLASSPATH配置到環(huán)境變量中去即可,因?yàn)檫@個(gè)可以通用。
          CATALINA_HOME和CATALINA_BASE的區(qū)別。簡(jiǎn)單的說(shuō),CATALINA_HOME是Tomcat的安裝目 錄,CATALINA_BASE是Tomcat的工作目錄。如果我們想要運(yùn)行Tomcat的 多個(gè)實(shí)例,但是不想安裝多個(gè)Tomcat軟件副本。那么我們可以配置多個(gè)工作 目錄,每個(gè)運(yùn)行實(shí)例獨(dú)占一個(gè)工作目錄,但是共享同一個(gè)安裝目錄

          下面講具體的配置方法。

          找到Tomcat的startup.sh文件,打開(kāi)進(jìn)行編輯。

          在文件的開(kāi)始位置,可以在一大堆注釋的后面,加入
          export CATALINA_BASE=/usr/ratest/apache-tomcat-7.0.16
          export CATALINA_HOME=/usr/ratest/apache-tomcat-7.0.16

          /usr/ratest/apache-tomcat-7.0.16這個(gè)就是tomcat的安裝文件夾位置,不同的tomcat指定相應(yīng)的文件夾即可。

          注意,這兩句話一定要在exec “$PRGDIR”/”$EXECUTABLE” start “$@”這句話的前面,我們放在文件的開(kāi)始位置了,所以,就可以不考慮了。

          然后就是要修改shutdown.sh文件,同樣在頭部加入上面的兩行即可,也要在exec “$PRGDIR”/”$EXECUTABLE” stop “$@”的前面。

          好了,解決了第一個(gè)問(wèn)題,下面說(shuō)第二個(gè)問(wèn)題的解決方法。

          找到并打開(kāi)server.xml文件,里面有諸如8080,8009,8443等等端口配置,統(tǒng)一給這些數(shù)字加上100,或者1000或者其他什么數(shù)字,只要是不跟其他Tomcat或者當(dāng)前l(fā)inux上其他服務(wù)的端口重復(fù)即可。

          現(xiàn)在進(jìn)入Tomcat的bin文件夾,運(yùn)行./startup.sh看看是不是可以啟動(dòng)多個(gè)了。

          posted @ 2014-03-28 19:58 為自己代言 閱讀(466) | 評(píng)論 (0)編輯 收藏

          1,設(shè)置跟路徑時(shí),三種方式

          在Tomcat默認(rèn)安裝后,tomcat的主目錄是webapps/root目錄,所以如果想改變tomcat的主目錄的話可以如下所做,所以
          第一種方法是:
          打開(kāi)C:/Tomcat/conf/server.xml,在<host></host>之間
          加入代碼:<Context docBase="d:/Tomcat 5.5/webapps/medi" path="" debug="0" reloadable="true"/>
          這樣重新啟動(dòng)tomcat,我們的主目錄就被設(shè)置為dolphin這個(gè)項(xiàng)目了。

          第二種方法是:
          將tomcat安裝目錄下的ROOT下的所有文件全部刪除,然后將工程的解壓后的文件全部拷進(jìn)去。

          第三種方法是:
          Tomcat5.0以下版本在d:/Tomcat/conf/Catalina/localhost目錄下會(huì)自動(dòng)生成了一個(gè)ROOT.Xml,
          但是5.0以上版本不再生成此文件,所以可以新建個(gè)ROOT.xml,在里面加入如下代碼:
          <?Xml version='1.0' encoding='utf-8'?>
          <Context crossContext="true" docBase="d:/Tomcat 5.5/webapps/medi" path="" reloadable="true">
          </Context>

          2,注意
          此時(shí)即然配置了 默認(rèn)訪問(wèn)目錄,則不要再tomcat 部署工程了,
          比如 在 conf\Catalina\localhost 增加配置文件 指定的工程路徑 此文件要去掉, 否則會(huì)重復(fù)加載工程。

          posted @ 2013-11-06 18:44 為自己代言 閱讀(696) | 評(píng)論 (0)編輯 收藏

          很多tomcat進(jìn)程退出(或者進(jìn)程假死),都是由于頻繁的拋出OutOfMemeoryError導(dǎo)致的。

          為了讓tomcat退出前或者發(fā)生OutOfMemeoryError時(shí)自動(dòng)dump堆棧信息,方便事后排查問(wèn)題,我們可以做如下操作:

          1、 在tomcat啟動(dòng)參數(shù)中加入兩個(gè)參數(shù) -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/export/home/tomcat/domains/server2/oom.hprof

          2、 重啟tomcat

          參數(shù)說(shuō)明
          (1)-XX:+HeapDumpOnOutOfMemoryError 表示當(dāng)JVM發(fā)生OOM時(shí),自動(dòng)生成DUMP文件。
          (2)-XX:HeapDumpPath=存儲(chǔ)文件/目錄 表示生成DUMP文件的路徑

          posted @ 2013-10-30 13:51 為自己代言 閱讀(6212) | 評(píng)論 (2)編輯 收藏

          想從JAVA轉(zhuǎn)到obj-c,看了些基礎(chǔ)的東西,感覺(jué)很奇怪,可能不太習(xí)慣,在網(wǎng)上看到一個(gè)不錯(cuò)文章有助理解obj-c的一些核心機(jī)制。

          Objective-C——消息、Category和Protocol

          2012-06-22 20:13 by 池建強(qiáng), 2627 閱讀, 11 評(píng)論, 收藏, 編輯

          面向?qū)ο笥肋h(yuǎn)是個(gè)可以吐槽的話題,從開(kāi)始提出到推崇備至,到充滿質(zhì)疑,一路走來(lái)讓人唏噓不已。面向?qū)ο蟮乃枷肟芍^歷史悠久,20世紀(jì)70年代的Smalltalk可以說(shuō)是面向?qū)ο笳Z(yǔ)言的經(jīng)典,直到今天我們依然將這門語(yǔ)言視為面向?qū)ο笳Z(yǔ)言的基礎(chǔ)。
          面向?qū)ο笫谴蟛糠志幊陶Z(yǔ)言的基本特性,像 C++、Java、Objective-C這樣的靜態(tài)語(yǔ)言,Ruby、Python這樣的動(dòng)態(tài)語(yǔ)言都是面向?qū)ο蟮恼Z(yǔ)言。但是如何編寫面向?qū)ο蟮某绦騾s一直是困擾人們的話題,即使是Smalltalk,也有人認(rèn)為這是一個(gè)有缺陷的面向?qū)ο蟮恼Z(yǔ)言實(shí)現(xiàn)。
           
          我在2010年翻譯過(guò)的一篇InfoQ的文章,《面向?qū)ο缶幊?#9472;─走錯(cuò)了路》中提到,面向?qū)ο缶幊痰娜齻€(gè)原則是:基于消息傳遞機(jī)制,對(duì)象分離和多態(tài)。文章中還舉了Erlang例子,認(rèn)為Erlang具備了這些原則,“所以可能是唯一的面向?qū)ο笳Z(yǔ)言”。除了之前提到的三個(gè)特征,單繼承和動(dòng)態(tài)類型也被引用為面向?qū)ο笳Z(yǔ)言的“絕對(duì)需求”。基于這些考慮,文章指出,Smalltalk在實(shí)現(xiàn)對(duì)象思想時(shí)的“錯(cuò)誤”──例如,只關(guān)注狀態(tài)和行為,在類和基于映像的語(yǔ)言里缺乏良好的并發(fā)模型和消息機(jī)制。
           
          這篇文章中的核心就是,面向?qū)ο笏枷胫谐藢?duì)象的狀態(tài)、行為,還應(yīng)該關(guān)注其并發(fā)機(jī)制、消息機(jī)制,后者更為重要。這一點(diǎn)事實(shí)上是我在接觸了Objective-C之后才有了更深入的體會(huì)。
           
          Ojbective-C的語(yǔ)法設(shè)計(jì)主要基于Smalltalk,除了提供傳統(tǒng)的面向?qū)ο缶幊烫匦灾猓€增加了很多類似動(dòng)態(tài)語(yǔ)言Ruby、Python才具有的特性,例如動(dòng)態(tài)類型、動(dòng)態(tài)加載、動(dòng)態(tài)綁定等等,同時(shí)強(qiáng)化了消息傳遞機(jī)制和表意(Intention Revealing Interface)接口的概念。
           
          —消息—
          消息傳遞模型(Message Passing)是Objective-C語(yǔ)言的核心機(jī)制。在Objective-C中,沒(méi)有方法調(diào)用這種說(shuō)法,只有消息傳遞。在C++或Java中調(diào)用某個(gè)類的方法,在Objective-C中是給該類發(fā)送一個(gè)消息。在C++或Java里,類與類的行為方法之間的關(guān)系非常緊密,一個(gè)方法必定屬于一個(gè)類,且于編譯時(shí)就已經(jīng)綁定在一起,所以你不可能調(diào)用一個(gè)類里沒(méi)有的方法。而在Objective-C中就比較簡(jiǎn)單了,類和消息之間是松耦合的,方法調(diào)用只是向某個(gè)類發(fā)送一個(gè)消息,該類可以在運(yùn)行時(shí)再確定怎么處理接受到的消息。也就是說(shuō),一個(gè)類不保證一定會(huì)響應(yīng)接收到的消息,如果收到了一個(gè)無(wú)法處理的消息,那么程序就是簡(jiǎn)單報(bào)一個(gè)錯(cuò)。甚至你可以向一個(gè)值為nil的空對(duì)象發(fā)送消息,系統(tǒng)都不會(huì)出錯(cuò)或宕掉。這種設(shè)計(jì)本身也比較符合軟件的隱喻。
           
          在表意接口(Intention Revealing Interface)方面,Objective-C也是設(shè)計(jì)的比較出色的語(yǔ)言。面向?qū)ο笳Z(yǔ)言的特性之一就是通過(guò)API把實(shí)現(xiàn)封裝起來(lái),為上層建筑提供服務(wù)。但是需要注意的一點(diǎn)就是,你封裝的API最好能夠讓調(diào)用者看到接口描述就知道怎么使用。如果為了使用一個(gè)API必須要去研究它的實(shí)現(xiàn),那么就失去了封裝的意義。Objective-C通過(guò)顯式的API描述,讓開(kāi)發(fā)者不自覺(jué)的寫出滿足表意接口的API,比如下圖中的API描述。

           
          上圖中描述了一個(gè)傳統(tǒng)意義的實(shí)例方法,但和Java或C++不同的是,其方法關(guān)鍵字由多個(gè)字符串組成,在這個(gè)例子是insertObject和atIndex,(id)anObject和 (NSUInterger)index分別表示參數(shù)類型和參數(shù)名稱。整個(gè)方法看上去就像一個(gè)英語(yǔ)句子,我們可以很容易的知道,這個(gè)方法就是在索引為 index處插入一個(gè)對(duì)象。如果你是從其他語(yǔ)言轉(zhuǎn)到Objective-C,那么開(kāi)始的時(shí)候會(huì)感覺(jué)這種寫法有些繁復(fù),但是一旦理解并習(xí)慣了你會(huì)感受到其巨大的好處,這種寫法會(huì)強(qiáng)制你寫出優(yōu)美易讀的代碼和API,而且有了XCode強(qiáng)大的提示功能,再長(zhǎng)的方法也是一蹴而就。
           
          下面我們來(lái)說(shuō)說(shuō)多態(tài)和繼承。
           與Java一樣,Objective-C一樣不支持多重繼承,但是通過(guò)類別(Category)和協(xié)議(Protocol)可以很好的實(shí)現(xiàn)代碼復(fù)用和擴(kuò)展。
           
          —Category—
          首先我們來(lái)談?wù)凜ategory。
           
          Objective-C提供了一種與眾不同的方式——Catagory,可以動(dòng)態(tài)的為已經(jīng)存在的類添加新的行為。這樣可以保證類的原始設(shè)計(jì)規(guī)模較小,功能增加時(shí)再逐步擴(kuò)展。使用Category 對(duì)類進(jìn)行擴(kuò)展時(shí),不需要訪問(wèn)其源代碼,也不需要?jiǎng)?chuàng)建子類。Category使用簡(jiǎn)單的方式,實(shí)現(xiàn)了類的相關(guān)方法的模塊化,把不同的類方法分配到不同的分類文件中。
           
          實(shí)現(xiàn)起來(lái)很簡(jiǎn)單,我們舉例說(shuō)明。
          SomeClass.h
          @interface SomeClass : NSObject{
          }
          -(void) print;
          @end 
           
          這是類SomeClass的聲明文件,其中包含一個(gè)實(shí)例方法print。如果我們想在不修改原始類、不增加子類的情況下,為該類增加一個(gè)hello的方法,只需要簡(jiǎn)單的定義兩個(gè)文件 SomeClass+Hello.h和SomeClass+Hello.m,在聲明文件和實(shí)現(xiàn)文件中用“()”把Category的名稱括起來(lái)即可。聲明文件代碼如下:
           
          #import "SomeClass.h"
           
          @interface SomeClass (Hello)
          -(void)hello;
          @end
          實(shí)現(xiàn)文件代碼如下
          #import "SomeClass+Hello.h"
          @implementationSomeClass (Hello)
          -(void)hello{
              NSLog (@"name:%@ ", @"Jacky");
          }
          @end 
          其中Hello是Category的名稱,如果你用XCode創(chuàng)建Category,那么需要填寫的內(nèi)容包括名稱和要擴(kuò)展的類的名稱。這里還有一個(gè)約定成俗的習(xí)慣,將聲明文件和實(shí)現(xiàn)文件名稱統(tǒng)一采用“原類名+Category”的方式命名。
          調(diào)用也非常簡(jiǎn)單,毫無(wú)壓力,如下:
          首先引入Category的聲明文件,然后正常調(diào)用即可。
          #import "SomeClass+Hello.h"
           
          SomeClass * sc =[[SomeClass alloc] init];
          [sc hello] 
          執(zhí)行結(jié)果是:
          nameJacky 
           
          Category的使用場(chǎng)景:
          1、當(dāng)你在定義類的時(shí)候,在某些情況下(例如需求變更),你可能想要為其中的某個(gè)或幾個(gè)類中添加方法。
          2、一個(gè)類中包含了許多不同的方法需要實(shí)現(xiàn),而這些方法需要不同團(tuán)隊(duì)的成員實(shí)現(xiàn)
          3、當(dāng)你在使用基礎(chǔ)類庫(kù)中的類時(shí),你可能希望這些類實(shí)現(xiàn)一些你需要的方法。
           
          遇到以上這些需求,Category可以幫助你解決問(wèn)題。當(dāng)然,使用Category也有些問(wèn)題需要注意,
          1、Category可以訪問(wèn)原始類的實(shí)例變量,但不能添加變量,如果想添加變量,可以考慮通過(guò)繼承創(chuàng)建子類。
          2、Category可以重載原始類的方法,但不推薦這么做,這么做的后果是你再也不能訪問(wèn)原來(lái)的方法。如果確實(shí)要重載,正確的選擇是創(chuàng)建子類。
          3、和普通接口有所區(qū)別的是,在分類的實(shí)現(xiàn)文件中可以不必實(shí)現(xiàn)所有聲明的方法,只要你不去調(diào)用它。
           
          用好Category可以充分利用Objective-C的動(dòng)態(tài)特性,編寫出靈活簡(jiǎn)潔的代碼。
           
          —Protocol— 
          下面我們?cè)賮?lái)看Protocol。
          Protocol,簡(jiǎn)單來(lái)說(shuō)就是一系列不屬于任何類的方法列表,其中聲明的方法可以被任何類實(shí)現(xiàn)。這種模式一般稱為代理(delegation)模式。你通過(guò)Protocol定義各種行為,在不同的場(chǎng)景采用不同的實(shí)現(xiàn)方式。在iOS和OS X開(kāi)發(fā)中,Apple采用了大量的代理模式來(lái)實(shí)現(xiàn)MVC中View和Controller的解耦。
           
          定義Protocol很簡(jiǎn)單,在聲明文件(h文件)中通過(guò)關(guān)鍵字@protocol定義,然后給出Protocol的名稱,方法列表,然后用@end表示Protocol結(jié)束。在@end指令結(jié)束之前定義的方法,都屬于這個(gè)Protocol。例如:
          @protocol ProcessDataDelegate <NSObject>
          @required
          - (void) processSuccessful: (BOOL)success;
          
          @optional
          - (id) submitOrder: (NSNumber *) orderid;
          @end
           
          以上代碼可以單獨(dú)放在一個(gè)h文件中,也可以寫在相關(guān)類的h文件中,可以視具體情況而定。該P(yáng)rotocol包含兩個(gè)方法,processSuccessful和submitOrder。這里還有兩個(gè)關(guān)鍵字,@required和@optional,表示如果要實(shí)現(xiàn)這個(gè)協(xié)議,那么processSuccessful方法是必須要實(shí)現(xiàn)的,submitOrder則是可選的,這兩個(gè)注解關(guān)鍵字是在Objective-C 2.0之后加入的語(yǔ)法特性。如果不注明,那么方法默認(rèn)是@required的,必須實(shí)現(xiàn)。
           
          那么如何實(shí)現(xiàn)這個(gè)Protocol呢,很簡(jiǎn)單,創(chuàng)建一個(gè)普通的Objective-C類,取名為TestAppDelegate,這時(shí)會(huì)生成一個(gè)h文件和m文件。在h文件中引入包含Protocol的h文件,之后聲明采用這個(gè)Protocol即可,如下:
          @interface TestAppDelegate : NSObject<ProcessDataDelegate>;
          
          @end
          用尖括號(hào)(<...>)括起來(lái)的ProcessDataDelegate就是我們創(chuàng)建的Protocol。如果要采用多個(gè)Protocol,可以在尖括號(hào)內(nèi)引入多個(gè)Protocol 名稱,并用逗號(hào)隔開(kāi)即可。例如<ProcessDataDelegate,xxxDelegate>
           
          m文件如下:
          @implementation TestAppDelegate
          
          - (void) processSuccessful: (BOOL)success{
              if (success) {
                  NSLog(@"成功");
              }else {
                  NSLog(@"失敗");
              }
          }
          
          @end 
          由于submitOrder方法是可選的,所以我們可以只實(shí)現(xiàn)processSuccessful。
           
          Protocol一般使用在哪些場(chǎng)景呢?Objective-C里的Protocol和Java語(yǔ)言中的接口很類似,如果一些類之間沒(méi)有繼承關(guān)系,但是又具備某些相同的行為,則可以使用 Protocol來(lái)描述它們的關(guān)系。不同的類,可以遵守同一個(gè)Protocol,在不同的場(chǎng)景下注入不同的實(shí)例,實(shí)現(xiàn)不同的功能。其中最常用的就是委托代理模式,Cocoa框架中大量采用了這種模式實(shí)現(xiàn)數(shù)據(jù)和UI的分離。例如UIView產(chǎn)生的所有事件,都是通過(guò)委托的方式交給Controller完成。根據(jù)約定,框架中后綴為Delegate的都是Protocol,例如UIApplicationDelegate,UIWebViewDelegate 等,使用時(shí)大家可以留意一下,體會(huì)其用法。
           
          使用Protocol時(shí)還需要注意的是:
          1、Protocol本身是可以繼承的,比如:
          @protocol A
               -(void)methodA;
          @end
          @protocol B <A>
               -(void)methodB;
          @end

          如果你要實(shí)現(xiàn)B,那么methodA和methodB都需要實(shí)現(xiàn)。 

          2、Protocol是類無(wú)關(guān)的,任何類都可以實(shí)現(xiàn)定義好的Protocol。如果我們想知道某個(gè)類是否實(shí)現(xiàn)了某個(gè)Protocol,還可以使用conformsToProtocol進(jìn)行判斷,如下:
          [obj conformsToProtocol:@protocol(ProcessDataDelegate)] 
           
          好吧,具體的語(yǔ)言特性這次就介紹這么多。從某種意義上來(lái)說(shuō),Objective-C是一門古老的語(yǔ)言,發(fā)明于1980年。1988年,喬布斯的Next公司獲得了Objective-C語(yǔ)言的授權(quán),并開(kāi)發(fā)出了Objective-C的語(yǔ)言庫(kù)和NEXTSTEP的開(kāi)發(fā)環(huán)境。NextStep是以Mach和BSD為基礎(chǔ),Objective-C是其語(yǔ)言和運(yùn)行庫(kù),后來(lái)的事大家都清楚,蘋果買了Next,喬布斯回歸蘋果,開(kāi)始神奇的蘋果振興之路,NextStep成了Max OS X的基礎(chǔ)。以后發(fā)展越來(lái)越好,Objctive-C成了Apple的當(dāng)家語(yǔ)言,現(xiàn)在基本上是Apple在維護(hù)Objctive-C的發(fā)展。
           
          在蘋果的AppStore推出之前,Objective-C一直相對(duì)小眾,但是其優(yōu)秀的語(yǔ)言特性似乎一直在為后面的爆發(fā)積蓄力量,當(dāng)蘋果平臺(tái)級(jí)的應(yīng)用出現(xiàn)之后,Objective-C開(kāi)始大放異彩,靜態(tài)語(yǔ)言的效率和動(dòng)態(tài)語(yǔ)言的特性得到眾多程序員的喜愛(ài),目前它已經(jīng)以火箭般的速度躥升TIOBE語(yǔ)言排行版第四位。
           
          對(duì)于喜愛(ài)蘋果技術(shù)的技術(shù)人員來(lái)說(shuō),Objective-C是你必須深入了解和值得學(xué)習(xí)的一門語(yǔ)言,希望以后有機(jī)會(huì)多寫一些相關(guān)的文章。

          轉(zhuǎn)載于:http://www.cnblogs.com/chijianqiang/archive/2012/06/22/objc-category-protocol.html

           

          posted @ 2012-10-17 13:49 為自己代言 閱讀(358) | 評(píng)論 (0)編輯 收藏

          主站蜘蛛池模板: 垣曲县| 伽师县| 黎城县| 河源市| 虞城县| 永春县| 郧西县| 临汾市| 南乐县| 云龙县| 阿克| 奉新县| 扶绥县| 新建县| 新余市| 漳州市| 松阳县| 南陵县| 郧西县| 三河市| 罗甸县| 临颍县| 镇赉县| 赤水市| 宜黄县| 双柏县| 农安县| 洱源县| 临江市| 宣恩县| 娄烦县| 莱芜市| 贺兰县| 伊金霍洛旗| 前郭尔| 辽阳县| 安庆市| 北安市| 永新县| 绥棱县| 左云县|