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

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

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

    莊周夢蝶

    生活、程序、未來
       :: 首頁 ::  ::  :: 聚合  :: 管理


        寫這篇文章的想法產(chǎn)生在昨天晚上讀《面向?qū)ο蠓治雠c設(shè)計(jì)》的時候,我漸漸發(fā)現(xiàn)我們這個小組不知不覺地貫徹了很多非常有價值的實(shí)踐經(jīng)驗(yàn),這些點(diǎn)點(diǎn)滴滴都對我們的最終的產(chǎn)品質(zhì)量產(chǎn)生了或大或小的影響,保證我們的系統(tǒng)不會出現(xiàn)重大的故障。我想有必要將這些“隱性知識”稍微總結(jié)一下,以供參考和記錄。

       從過程的連續(xù)光譜來看,我們大概處于中間位置偏左的位置,更偏向一個輕量級團(tuán)隊(duì)的敏捷過程,但是也包含計(jì)劃驅(qū)動過程中的因素。我們的小組是自管理的,沒有專門的QA和SA,我們自己去想出最好的工作方法,但是在執(zhí)行中我們的計(jì)劃還是相對確定的,每個季度做什么都會有一個比較明確的計(jì)劃和里程碑,并且對問題領(lǐng)域都相對熟悉;我們的過程是迭代式,一般一個季度至少會交付一個穩(wěn)定可執(zhí)行的新版本,我們在文檔上做的不是特別好,很多都依賴于團(tuán)隊(duì)成員之間的“隱性知識”;同時我們對問題的改進(jìn)基本還是有一個流程和機(jī)制,會持續(xù)的跟蹤問題并改進(jìn)。

       下面分階段總結(jié)下我們的一些實(shí)踐經(jīng)驗(yàn)。

    一、分析和設(shè)計(jì)階段

    1、在這個階段,我們會明確準(zhǔn)備做什么,界定問題的邊界,對功能進(jìn)行一個取舍。一般在一個版本完成之后會馬上開始這個過程。大家都想一想接下來做什么,經(jīng)過幾輪PK后確定重要緊急的事情優(yōu)先做,定義下一個版本的功能列表

    2、功能列表出來之后,我們會針對每個功能提出各種方案做比較,在此期間,我們會邀請更大團(tuán)隊(duì)范圍內(nèi)的專家參與方案和設(shè)計(jì)的評審,剔除不切實(shí)際以及明顯有缺陷的方案,針對一些風(fēng)險(xiǎn)點(diǎn)提出改進(jìn)建議和防范措施。

    3、在設(shè)計(jì)方案出來之后,我們會分配功能的開發(fā)任務(wù),根據(jù)每個開發(fā)人員熟悉的領(lǐng)域,自主領(lǐng)取或者被動分配任務(wù)。這個過程不是一成不變的,考慮到團(tuán)隊(duì)內(nèi)部知識交流的必要性,也可能讓不熟悉某個領(lǐng)域的人去做他不熟悉的事情。

    二、構(gòu)造階段

    1、整個系統(tǒng)已經(jīng)有一個關(guān)鍵的抽象機(jī)制,針對我們的服務(wù)器有一個核心的pipeline機(jī)制,針對我們的客戶端,有一個核心的發(fā)送消息流程。將所有的功能模塊組織在這個關(guān)鍵機(jī)制周圍,形成一個強(qiáng)有力的整體。

    2、開發(fā)完成不僅僅意味著功能代碼的完成,還包括測試代碼:單元測試和集成測試。如果你沒辦法做到全面的覆蓋,那就要求必須覆蓋運(yùn)行的關(guān)鍵路徑和極端場景。

    3、單元測試我們使用JUnit,適當(dāng)使用Mock可以簡化測試。但是Mock對象如果太多,也許會失去測試的價值,這里有一個權(quán)衡。

    4、在整個構(gòu)造過程中,我們貫徹每日構(gòu)建、持續(xù)集成的原則。使用hudson做持續(xù)集成,時刻關(guān)注測試狀況,有問題及時反饋給開發(fā)者。

    5、有一個功能強(qiáng)大的集成測試框架,模擬實(shí)際環(huán)境做各種測試,它的目的是盡量在接近真實(shí)狀況下去執(zhí)行系統(tǒng)并盡早暴露問題。

    6、每個功能完成之后,立即發(fā)起review,請同事和你一起復(fù)審代碼。復(fù)審代碼的作用不僅是發(fā)現(xiàn)bug,改良設(shè)計(jì),也是一個知識交流的最佳途徑。我們經(jīng)常能通過代碼審查發(fā)現(xiàn)一些設(shè)計(jì)上的缺陷,以及功能實(shí)現(xiàn)上的BUG。我們團(tuán)隊(duì)?wèi)?yīng)該說是非常看重代碼審查的作用。

    7、使用findbugs和clover等工具,分析代碼質(zhì)量并改進(jìn)。

    8、在發(fā)布之前,做一次集中的代碼review,每個人介紹下自己的功能實(shí)現(xiàn)代碼和設(shè)計(jì),一般我們會申請一個會議室和投影儀,并邀請團(tuán)隊(duì)之外的人加入review。

    9、在發(fā)布之前,有一個系統(tǒng)的壓測流程,針對每個版本更新壓測方案,并預(yù)留一到兩周的時間做性能壓測。壓測不僅能盡早暴露性能隱患,還可以發(fā)現(xiàn)系統(tǒng)在特殊情況下的一些BUG。壓測除了關(guān)注系統(tǒng)的吞吐量、GC情況之外,還應(yīng)該關(guān)注硬件的性能指標(biāo)。

    三、發(fā)布和總結(jié)
    1、發(fā)布之前,最好讓使用我們系統(tǒng)的用戶使用新版本做一個回歸測試,一方面是測試兼容性,一方面也可以及早發(fā)現(xiàn)BUG。

    2、我們的發(fā)布流程:線下、beta、線上。每個階段通常都持續(xù)一到兩周,才會進(jìn)行到下一階段。并且是從相對不重要的系統(tǒng),到關(guān)鍵系統(tǒng)的順序進(jìn)行發(fā)布。

    3、發(fā)布之后,通過日志、運(yùn)行時監(jiān)控、用戶反饋等方式收集系統(tǒng)運(yùn)行狀況,發(fā)現(xiàn)BUG,修正BUG,補(bǔ)充測試,測試通過,重新發(fā)布。

    4、每個版本發(fā)布后,需要總結(jié)下本次發(fā)布過程中遇到的所有BUG以及經(jīng)驗(yàn)教訓(xùn),并提出可能的改進(jìn)建議。

    5、需要一個跟蹤線上問題的BUG跟蹤系統(tǒng),可以用JIRA之類的trace軟件。跟蹤不僅是記錄,最好列出解決的時間點(diǎn),在哪個版本確定解決,甚至確定交給誰去解決,并持續(xù)跟進(jìn)。

    posted @ 2010-12-30 11:01 dennis 閱讀(4251) | 評論 (9)編輯 收藏


        Xmemcached在元旦左右準(zhǔn)備發(fā)1.3這個版本,這個版本新增加的一個關(guān)鍵特性就是所謂的failure模式。關(guān)于這個,可以看下memcached官方文檔的解釋
    《Failure,or Failover》

        展開來說,在某個memcached節(jié)點(diǎn)掛掉或者由于其他故障連接斷開的時候,大部分客戶端的默認(rèn)策略都是failover的,也就是會查找下一個可用的memcached節(jié)點(diǎn)繼續(xù)使用,掛掉或者連接不上的節(jié)點(diǎn)的數(shù)據(jù)會轉(zhuǎn)移到其他節(jié)點(diǎn)上,路由的策略可以是Round Robin,也可以是一致性哈希。這樣的模式在節(jié)點(diǎn)意外故障掛掉的情況下運(yùn)行的很好,但是memached節(jié)點(diǎn)也完全可能因?yàn)橐粋€意外的事故而短暫掛掉,比如你不小心弄掉了網(wǎng)線又馬上接上去,比如機(jī)房交換機(jī)突然停電又立即恢復(fù)了,假設(shè)在故障前,用戶A正要更新數(shù)據(jù)到節(jié)點(diǎn)A,節(jié)點(diǎn)A意外斷開,那么這些數(shù)據(jù)就更新到下一個有效節(jié)點(diǎn)B,但是節(jié)點(diǎn)A又馬上恢復(fù),這時候用戶又從節(jié)點(diǎn)A去讀數(shù)據(jù),讀到卻是更新前的老數(shù)據(jù)了(新數(shù)據(jù)更新到B節(jié)點(diǎn)去了),這種情況對用戶來說就非常困惑,你告訴我更新成功,但是看到卻還是更新前的數(shù)據(jù)。

       怎么解決呢?一個簡單的方案就是所謂failure模式,當(dāng)某個節(jié)點(diǎn)掛掉的時候,不會從節(jié)點(diǎn)列表移除,請求也不會轉(zhuǎn)移到下一個有效節(jié)點(diǎn),而是直接將請求置為失敗,就剛才的場景來說,在用戶更新數(shù)據(jù)到節(jié)點(diǎn)A的時候,節(jié)點(diǎn)A意外斷開,那么用戶的這次更新請求不會轉(zhuǎn)移到節(jié)點(diǎn)B,而是直接告訴用戶更新失敗,用戶再次查詢數(shù)據(jù)則繞過節(jié)點(diǎn)A直接查詢后端存儲。這種模式很適合這種節(jié)點(diǎn)短暫不可用的狀況,請求會穿透緩存到后端,但是避免了新舊數(shù)據(jù)的問題。
        Xmemcached 1.3將支持failure模式,只要你設(shè)置下failureMode為true即可,簡單示例:

               XMemcachedClientBuilder builder =……
                   
    //設(shè)置使用failure模式
               builder.setFailureMode(true);
          在此模式下,某個節(jié)點(diǎn)掛掉的情況下,往這個節(jié)點(diǎn)的請求都將直接拋出MemcachedException的異常。

          不僅如此,xmemcached 1.3還將引入standby node的概念,你可以設(shè)置某個memached節(jié)點(diǎn)的備份節(jié)點(diǎn),當(dāng)這個節(jié)點(diǎn)掛掉的時候會將請求轉(zhuǎn)發(fā)給這個備份節(jié)點(diǎn),不會簡單地拋出異常,也不會轉(zhuǎn)發(fā)給其他節(jié)點(diǎn)。要使用standby node,必須首先設(shè)置使用failure mode,一個例子:

    XMemcachedClientBuilder builder = new XMemcachedClientBuilder(AddrUtil
                    .getAddressMap(
    "192.168.1.99:11211,192.168.1.100:11211 192.168.1.101:11211,192.168.1.102:11211"));
    builder.setFailureMode(
    true);

         可以看到,新的服務(wù)器字符串格式變化為host:port,host:port host:port,host:port的格式,以空格隔開的是兩個節(jié)點(diǎn)組成的一個分組,以逗號隔開的是主節(jié)點(diǎn)和備份節(jié)點(diǎn),以上面的例子來說,我們設(shè)置客戶端使用的節(jié)點(diǎn)是192.168.1.99和192.168.1.101,其中99對應(yīng)的備份節(jié)點(diǎn)是100,而101的備份節(jié)點(diǎn)是102。并且我們需要設(shè)置使用failure mode為true。
       
         Failure mode加上standby節(jié)點(diǎn)可以比較好的解決新舊數(shù)據(jù)的問題,并且也可以防止請求穿透緩存到DB,但是主備兩個節(jié)點(diǎn)之間的數(shù)據(jù)同步,xmemcached不準(zhǔn)備幫你做,我的建議是可以使用repcached這個patch做復(fù)制。
        有的朋友可能希望,在使用備份節(jié)點(diǎn)之前先flush掉備份節(jié)點(diǎn)的數(shù)據(jù),防止使用到老的數(shù)據(jù),請求還是可以穿透緩存去DB查找,并存儲到備份節(jié)點(diǎn),我仔細(xì)考慮了這個方案,衡量之下還是不準(zhǔn)備做自動flush,主要是并發(fā)上很難處理,并且flush數(shù)據(jù)這個事情可以手工來搞,根據(jù)我的經(jīng)驗(yàn),做的太透明太自動不一定是好事。你可以在主節(jié)點(diǎn)恢復(fù)之后,手工flush下備份節(jié)點(diǎn)的數(shù)據(jù)。


        目前,xmemcached 1.3已經(jīng)整裝待發(fā),對這些特性有興趣的朋友可以先從svn下載源碼嘗鮮,有任何改進(jìn)的建議請發(fā)郵件給我。我的郵件地址在博客的右上角。





      

    posted @ 2010-12-28 10:47 dennis 閱讀(4627) | 評論 (5)編輯 收藏

        最近沒更新blog,因?yàn)槲颐χ鴮懗绦颍鎍ndroid。入手了一臺moto xt701,下單后悔來想換里程碑,但是京東打包太快,客服不讓換,這個很奇怪,我想用更多錢買東西反而被拒絕,看來京東處理太快也不一定是優(yōu)點(diǎn)

        買了幾本android相關(guān)的書,下決心好好學(xué)習(xí)一下,在讀完一本加半本的情況下決定開始做個練習(xí)程序,最后的結(jié)果就是為樂天氣這個小軟件。為樂天氣不僅僅是個爛大街的天氣預(yù)報(bào)軟件,它從google weather api抓取天氣狀況信息并顯示在桌面widget,還提供了兩個我個人很需要但還沒有在其他天氣軟件上看到的功能:惡劣天氣告警和氣溫變化告警——當(dāng)有雨雪天氣或者氣溫變化超過一定幅度的時候主動通知我,這對我這個常常不知道帶傘并且家里有小孩的人比較有用。來幾張運(yùn)行在xt701上的截圖:






      

        寫程序總共花了3天,程序雖小,但基本上覆蓋了android提供的一些基本機(jī)制:Activity顯示組件、service負(fù)責(zé)信息抓取、桌面widget、通過intent在組件之間交互、handler處理界面更新、國際化和資源管理、利用preferences保存配置以及使用Application保存全局?jǐn)?shù)據(jù)等等。Android開發(fā)給我的感覺是,入門還是相當(dāng)容易的,如果熟悉Java甚至J2ME,那么學(xué)習(xí)android的入門成本還是很低的,因此從長期來看,做這一行的一般應(yīng)用門檻不高,也會像現(xiàn)在的Java市場一樣吸引大量開發(fā)者。如果說對獨(dú)立開發(fā)者特別有價值的方向,應(yīng)該還是游戲方向,做游戲不僅僅是技術(shù),更多還是創(chuàng)意和推廣,另外想在android做出效果非常出色的游戲,還需要去學(xué)習(xí)OpenGL和數(shù)學(xué)算法之類,需要熟悉c/c++,本質(zhì)上跟傳統(tǒng)的游戲開發(fā)沒有太大區(qū)別,這個門檻就相對高一些。

       我將這個小程序發(fā)到了國內(nèi)的幾個market,從下載情況來看,盡管都非常少,但是91助手的應(yīng)用匯還是最多,其次是安卓市場,再后面是愛米軟件商店,從后臺體驗(yàn)上來說,最好的還是eoeAndroid社區(qū)的優(yōu)智市場,不過人氣貌似不旺。
        從我接觸移動開發(fā)的這一周來看,我很興奮,原來現(xiàn)在這個行業(yè)已經(jīng)這么火熱,有太多新鮮的東西我沒有嘗試過,有太多很有創(chuàng)意的小應(yīng)用小游戲存在,有大量的開發(fā)者早就在從事這個激動人心的領(lǐng)域,我太out了,希望現(xiàn)在關(guān)注還來得及。

    posted @ 2010-12-11 19:35 dennis 閱讀(2633) | 評論 (2)編輯 收藏


        HandlerSocket是日本人 akira higuchi 寫的一個MySql的插件,通過這個插件,你可以直接跟MySql后端的存儲引擎做key-value式的交互,省去了MySql上層的SQL解釋、打開關(guān)閉表、創(chuàng)建查詢計(jì)劃等CPU消耗型的開銷,按照作者給出的數(shù)據(jù)可以在數(shù)據(jù)全部在內(nèi)存的情況下可以達(dá)到75W的QPS查詢。具體信息可以看這篇Blog,中文介紹可以看這篇文章《HandlerSocket in action》。

        這個東西為什么讓我很激動呢?首先性能是程序員的G點(diǎn),一聽高性能你不由地激動,其次,這也解決了緩存跟數(shù)據(jù)庫的一致性問題,因?yàn)榫彺婢驮跀?shù)據(jù)庫里面,第三,這個東西不僅僅是NoSQL,簡單的CRUD你可以通過HandlerSocket,但是復(fù)雜的查詢你仍然可以走M(jìn)ySql,完全符合我們應(yīng)用的場景,并且從實(shí)際測試來看,性能確實(shí)非常優(yōu)秀。但是呢,這個東西的代價也少不了,例如沒有權(quán)限檢查(未來可能添加);不能啟用MySql的查詢緩存,否則會導(dǎo)致數(shù)據(jù)的不一致;協(xié)議設(shè)計(jì)也不合理,使用\t做分隔符,使用\n做換行符,那么你插入或者更新的字段數(shù)據(jù)就不能含有這些字符,否則行為將不如預(yù)期。

       HandlerSocket有一個日本人的java客戶端實(shí)現(xiàn),我去嘗試了下,結(jié)果發(fā)現(xiàn)這玩意完全不具實(shí)用性,封裝的層次非常原始。因此我自己寫了個新的客戶端,這就是本文要介紹的HandlerSocket Client for Java,簡稱hs4j,項(xiàng)目放在了googlecode,代碼的網(wǎng)絡(luò)層復(fù)用xmemcached,重新實(shí)現(xiàn)了協(xié)議和上層接口,目前的狀態(tài)完全可用,也希望有需要的朋友參與測試。

       項(xiàng)目地址:http://code.google.com/p/hs4j/

        HS4J的使用很簡單,所有的操作都通過HSClient這個接口進(jìn)行,如我們創(chuàng)建一個客戶端對象:

    import com.google.code.hs4j.HSClient;
    import com.google.code.hs4j.impl.HSClientImpl;

       HSClient hsClient 
    = new HSClientImpl(new InetSocketAddress(9999));

       假設(shè)HandlerSocket運(yùn)行在本地的9999端口,默認(rèn)的9998是只讀的,9999才是允許讀和寫。HSClient是線程安全的。

       在執(zhí)行操作前需要先open index:
    import com.google.code.hs4j.IndexSession;

          IndexSession session 
    = hsClient.openIndexSession(db, table,
                                    
    "PRIMARY", columns);

       其中db是數(shù)據(jù)庫名,table是表名,"PRIMARY"表示使用主鍵索引,columns是一個字符串?dāng)?shù)組代表你要查詢的字段名稱。這里沒有指定indexid,默認(rèn)會產(chǎn)生一個indexid,你也可以指定indexid,返回表示一次open-index會話對象,IndexSession同樣是線程安全的。
    IndexSession session = hsClient.openIndexSession(indexid,db, table,
                                    
    "PRIMARY", columns);

       查詢操作通過find方法:
    import java.sql.ResultSet;

                    
    final String[] keys = { "dennis""killme2008@gmail.com" };
                    ResultSet rs 
    = session.find(keys);
                    
    while(rs.next()){
                       String name
    =rs.getString(1);
                       String mail
    =rs.getString(2);
                    }

       find返回的是java.sql.ResultSet,你完全可以像使用jdbc那樣去操作結(jié)果集。當(dāng)然我的簡單實(shí)現(xiàn)并不符合JDBC規(guī)范,只實(shí)現(xiàn)了最常見的一些方法,如getStrng、getLong等。find(keys)方法默認(rèn)使用的op是"="。其他重載方法可以設(shè)置其他類型的op,統(tǒng)一封裝為枚舉類型FindOperator。

       更新操作:
    import com.google.code.hs4j.FindOperator;

       
    int result=session.update(keys, new String[] { "1""dennis",
                                    
    "test@163.com""109" }, FindOperator.EQ);

       keys表示索引的字段列表對應(yīng)的值數(shù)組,通過FindOperator.EQ比較這些值和索引,第二個參數(shù)values表示要更新的字段值,這些值跟你在open-index的時候傳入的columns一一對應(yīng),最后返回作用的記錄數(shù)。

        刪除操作:
       int result= session.delete(new String[] { "dennis" },
                                    FindOperator.EQ)

        HS4J同樣支持連接池,可以在構(gòu)建客戶端的時候傳入連接池大小:
      //100-connections pool
       HSClient hsClient = new HSClientImpl(new InetSocketAddress(9999),100);

       在open index的時候,會在連接池里所有的連接上都o(jì)pen。并且在連接因?yàn)橐馔馇闆r(如網(wǎng)絡(luò)錯誤)斷開的時候,HS4J會自動重連,并在重連成功的情況下自動發(fā)送已經(jīng)open的index,保證應(yīng)用的操作不受重連影響。

        因?yàn)镠S4J是我在兩天內(nèi)寫就的一個東西,可能還有不少隱藏的bug,并且HandlerSocket本身也是個新東西,如果有什么問題或者改進(jìn)建議,隨時歡迎告訴我,多謝。


      
      

    posted @ 2010-11-30 13:51 dennis 閱讀(9018) | 評論 (4)編輯 收藏


         上周在內(nèi)部做的一個Java NIO框架的實(shí)現(xiàn)技巧和陷阱的分享,對編寫NIO網(wǎng)絡(luò)框架有興趣的朋友可能有點(diǎn)幫助,上傳slideshare.net一直出錯,直接提供下載吧。
        
         下載地址:Nio Trick and Trap.pdf.zip





    posted @ 2010-11-22 18:22 dennis 閱讀(14296) | 評論 (19)編輯 收藏


        前段時間對kilim的當(dāng)前版本做了一些改進(jìn),集中在nio調(diào)度器這一塊。Kilim新版本引入了nio調(diào)度器,可以跟非阻塞IO結(jié)合在一起,從這個版本開始,kilim才真正具有實(shí)用性。協(xié)程只有跟非阻塞IO結(jié)合起來才能發(fā)揮威力啊。但是Kilim的默認(rèn)的nio調(diào)度器還只是使用一個nio worker做調(diào)度,這跟現(xiàn)有的NIO框架采用多個nio worker來提升效率比較起來相對落伍。我改進(jìn)了NioSelectorScheduler,引入了類似Netty3的boss和woker的概念,boss負(fù)責(zé)連接接入,而worker負(fù)責(zé)連接的IO讀寫,并且默認(rèn)設(shè)置worker數(shù)目為CPU個數(shù)的兩倍。經(jīng)過我的測試,改進(jìn)后的NIO調(diào)度器的效率遠(yuǎn)遠(yuǎn)超過了現(xiàn)有的調(diào)度器,有興趣可以用netty的benchmark跑一下example里的EchoServer

        Kilim默認(rèn)還提供了一個簡易Http Server框架,但是沒有提供Http Client的實(shí)現(xiàn),我的另一個改進(jìn)是提供了一個簡易的Http Client實(shí)現(xiàn),也是利用Ragel做協(xié)議解析,一個簡單的使用例子如下:
    package kilim.examples;

    import kilim.Pausable;
    import kilim.Task;
    import kilim.http.HttpClient;
    import kilim.http.HttpResponse;


    public class SimpleHttpClient {
        
    static class SimpleTask extends Task {
            @Override
            
    public void execute() throws Pausable, Exception {
                HttpClient client 
    = new HttpClient();

                HttpResponse resp 
    = client.get("http://www.google.com.hk/");
                System.out.println(resp.status());
                System.out.println(resp.content());
            }
        }


        
    public static void main(String[] args) {
            SimpleTask task 
    = new SimpleTask();
            task.start();
        }

    }


        這個簡陋的HttpClient目前只支持GET/POST,同時支持Http chunk編碼(得益于kilim原有代碼),做一些簡單的HTTP調(diào)用已經(jīng)足夠。我嘗試在一個項(xiàng)目里使用這個HttpClient去替代java默認(rèn)的HttpURLConnection,效率有部分提升,但是同時由于大量協(xié)程存在占用了很大部分的內(nèi)存,給GC也帶來了不小的壓力。

        我的代碼直接從kilim的主干fork出來,有興趣可以直接git clone下來玩玩,地址  https://github.com/killme2008/kilim

    posted @ 2010-11-19 18:40 dennis 閱讀(3944) | 評論 (3)編輯 收藏

     
        一直有這么個想法,列一下我個人認(rèn)為在學(xué)習(xí)和使用Java過程中可以推薦一讀的書籍,給初學(xué)者或者想深入的朋友一些建議,幫助成長。推薦的的都是我自己讀過,也會推薦一些朋友讀過并且口碑不錯的書籍。

    一、基礎(chǔ)類
    1、《Thinking in java》,入門第一位是建立正確的概念。
    2、《Core Java》,我沒系統(tǒng)讀過,這本書更貼近實(shí)踐,更多API的介紹,同樣,更新也更頻繁。

    二、進(jìn)階類
    1、《Effective Java》,在熟悉語法、API之后,你需要知道最佳實(shí)踐和陷阱,沒有比這本更好的。
    2、《Java Puzzlers》,通過謎題介紹一些你可能沒有注意到的邊角料,作為趣味讀物也不錯
    3、《深入Java虛擬機(jī)》,翻譯一般,但不可不讀,最好結(jié)合最新的JVM規(guī)范來讀。

    三、特定領(lǐng)域
    1、網(wǎng)絡(luò)編程:
    (1)
    O'Reilly的《Java nio》,很多人都推薦,我個人覺的一般,基本上只是個API更詳細(xì)的說明文檔,O'reilly的java系列很多都是這樣。
    (2)我更推薦這本《Fundamental networking in java》,由淺入深教你怎么做java網(wǎng)絡(luò)編程,并且介紹很多背景知識,甚至介紹了各種最佳實(shí)踐、網(wǎng)絡(luò)編程模型以及Java socket在不同平臺之間的差異等等。

    2、并發(fā)編程:
    (1)《Java Concurrency in Practic》,并發(fā)領(lǐng)域必讀經(jīng)典。
    (2)《Java并發(fā)編程:設(shè)計(jì)原則與模式》,同樣是Doug lea的作品。
    (3) 《java threads》,入門讀物。

    3、web編程,這塊我許久未接觸了,就不推薦了,有興趣的朋友可以補(bǔ)充下。

    四、模式與設(shè)計(jì)

    1、《設(shè)計(jì)模式》,GOF的經(jīng)典。
    2、《設(shè)計(jì)模式精解》,應(yīng)該有最新版,個人認(rèn)為更適合入門。
    3、《Head first設(shè)計(jì)模式》,更輕松的入門讀物。
    4、《企業(yè)應(yīng)用架構(gòu)模式》
    5、《分析模式——可復(fù)用對象模型》
    6、《面向模式的軟件體系結(jié)構(gòu)》,國內(nèi)貌似翻譯了3卷,絕對經(jīng)典,可惜翻譯較差。
    7、《重構(gòu)——改善既有代碼設(shè)計(jì)》,想寫好代碼必讀。
    8、《重構(gòu)與模式》,給我印象很深的 xml構(gòu)建的例子,在我的代碼里應(yīng)用到了。

    五、方法論
    1、《敏捷軟件開發(fā)》
    2、《測試驅(qū)動開發(fā)》,你不一定要TDD,但是你一定要學(xué)會做單元測試。
    3、《Agile Java》,也可以作為java入門讀物。
    4、《快速軟件開發(fā)》
    5、《面向?qū)ο蠓治雠c設(shè)計(jì)》,OO設(shè)計(jì)必讀。
    6、《Unix編程藝術(shù)》,打開你的眼界。

    六、Java之外
    0、《代碼大全》,編程的百科全書,必讀。
    1、《unix網(wǎng)絡(luò)編程》,學(xué)習(xí)網(wǎng)絡(luò)編程必讀書。
    2、《C++網(wǎng)絡(luò)編程》上下兩卷,介紹ACE的,但是其中對各種模式運(yùn)用的介紹非常值的一讀。
    3、《Joel說軟件》,編程文化
    4、《人月神話》、《人件》
    5、《卓有成效的程序員》,給我很大啟發(fā)的一本書。
    6、《程序員修煉之道》
    7、《計(jì)算機(jī)程序的構(gòu)造與解釋》,必讀
    8、《算法導(dǎo)論》,可以作為參考書
    9、《深入理解計(jì)算機(jī)系統(tǒng)》
    10、《編譯原理》龍書,最新版用java解釋,我沒有讀完,順便提下。




    posted @ 2010-11-11 11:13 dennis 閱讀(20899) | 評論 (12)編輯 收藏


        我最近在實(shí)現(xiàn)一個基于Kilim的HttpClient,在處理響應(yīng)body特別大的情形下遇到了kilim的一個BUG,有必要記錄下。
        問題是這樣,Kilim將連接封裝為EndPoint對象,EndPoint有個方法fill用于從管道讀數(shù)據(jù)到緩沖區(qū),并且可以指定希望至少讀到多少個字節(jié)(atLeastN)才返回。那么在進(jìn)入此方法的時候會判斷緩沖區(qū)是否有足夠空間容納atLeastN個字節(jié),如果沒有,則創(chuàng)建一個更大的緩沖區(qū),并將“老”的緩沖區(qū)的數(shù)據(jù)拷貝到新緩沖區(qū),這部分代碼是這樣實(shí)現(xiàn):
    public ByteBuffer fill(ByteBuffer buf, int atleastN) throws IOException, Pausable {
            
    if (buf.remaining() < atleastN) {
                ByteBuffer newbb 
    = ByteBuffer.allocate(Math.max(buf.capacity() * 3 / 2, buf.position() + atleastN));
                buf.rewind();
                newbb.put(buf);
                buf 
    = newbb;
            }
            ……
    }

        后面的代碼我省略了,這個BUG就出現(xiàn)在這段代碼里。這段代碼的邏輯很簡單,先是創(chuàng)建一個新的更大的緩沖區(qū),然后將老的緩沖區(qū)的數(shù)據(jù)put到新的緩沖區(qū),在put之前調(diào)用rewind方法將老的緩沖區(qū)的position設(shè)置為0。查看rewind干了什么:
     public final Buffer rewind() {
        position 
    = 0;
        mark 
    = -1;
        
    return this;
        }

        僅僅是將position設(shè)置為0,并讓mark失效。position指向下一個讀或者寫的位置,這里在寫入到新緩沖區(qū)之前確實(shí)需要將position設(shè)置為0,以便寫入從老的緩沖區(qū)第一個位置開始。問題是什么?問題是position僅僅指定了下一個讀取數(shù)據(jù)的位置,卻沒有指定有效數(shù)據(jù)的大小,換句話說,沒有指定老的緩沖區(qū)的limit。因此這里造成的后果是老的緩沖區(qū)整個被寫入到新的老緩沖區(qū),包括有效數(shù)據(jù)和無效數(shù)據(jù),默認(rèn)情況下緩沖區(qū)的limit等于capacity。

       這個bug可以通過下面程序看出來:
            ByteBuffer old = ByteBuffer.allocate(8);
            old.putInt(
    99);
            ByteBuffer newBuf 
    = ByteBuffer.allocate(16);
            old.rewind();
            newBuf.put(old);
            newBuf.putInt(
    100);

            newBuf.flip();
            System.out.println(newBuf.remaining());
            System.out.println(newBuf.getInt());
            System.out.println(newBuf.getInt());
            System.out.println(newBuf.getInt());

        先往old寫入一個整數(shù)99,然后創(chuàng)建newBuf并寫入old數(shù)據(jù),并再寫入一個整數(shù)100,最后從newBuf讀數(shù)據(jù)。本來我們預(yù)期只應(yīng)該讀到兩個整數(shù)99和100,但是中間卻插入一個0,輸出如下:

    12
    99
    0
    100

        12表示緩沖區(qū)可讀的數(shù)據(jù),本來應(yīng)該是8個字節(jié),卻多了4個字節(jié)的無效數(shù)據(jù)。

         這個BUG解決很簡單,將rewind修改為flip方法即可,flip不僅將position設(shè)置為0,也將limit設(shè)置為當(dāng)前位置:
      public final Buffer flip() {
        limit 
    = position;
        position 
    = 0;
        mark 
    = -1;
        
    return this;
        }


        修改上面的測試程序,符合我們的預(yù)期了:
            ByteBuffer old = ByteBuffer.allocate(8);
            old.putInt(
    99);
            ByteBuffer newBuf 
    = ByteBuffer.allocate(16);
            old.flip();
            newBuf.put(old);
            newBuf.putInt(
    100);

            newBuf.flip();
            System.out.println(newBuf.remaining());
            System.out.println(newBuf.getInt());
            System.out.println(newBuf.getInt());;

        輸出:
    8
    99
    100

        總結(jié),使用rewind的前提是limit已經(jīng)正確設(shè)置,例如你將buffer寫入成功并想記錄這個buffer,可以使用rewind:
    while (buffer.hasRemaining()) //發(fā)送數(shù)據(jù)
        networkChannel.write(buffer);
    buffer.rewind(); 
    // 重置buffer,準(zhǔn)備寫入日志管道
    while (buffer.hasRemaining()) // 寫入日志
        loggerChannel.write(buffer);

       而flip用于緩沖區(qū)發(fā)送或者讀取之前,也就是將緩沖區(qū)設(shè)置為等待傳出狀態(tài)。

    posted @ 2010-11-03 23:22 dennis 閱讀(2802) | 評論 (2)編輯 收藏

        這某文就是這篇文章啦,咱也不廢話,不遮掩。幾點(diǎn)感想:

    1、首先,這篇文章很多的“我聽說”、“據(jù)說“、我和……聊脫,他們都表示很郁悶“、“我就聽說過一些……”諸如此類的道聽途說,我覺的這不是“工程師”該說的話,請不要用可能,好像,聽說這樣的字眼,請給我數(shù)據(jù),給我地點(diǎn),給我程序。

    2、其次,文中作者提到的yahoo的優(yōu)秀點(diǎn),在淘寶我都發(fā)現(xiàn)了傳承,我估計(jì)是學(xué)習(xí)yahoo中國的,比如發(fā)布流程、比如知識庫、比如很有特色的技術(shù)大學(xué)培訓(xùn)、比如鼓勵創(chuàng)建自動化工具等等,我不知道作者有沒有在阿里集團(tuán)的子公司待過,或者來淘寶待過,如果來過呆過,我想不會沒看見。

    3、第三,Google是商業(yè)公司,百度是商業(yè)公司,阿里更是商業(yè)公司,沒有銷售人員的付出,工程師的勞動成果何以體現(xiàn)?你的薪水從哪里來?工程師文化或者銷售文化,這不重要的,重要的是你能否認(rèn)同,能否感受到尊重,不能可以用腳投票。

    4、個人感覺,阿里是非常富有理想主義的公司,點(diǎn)滴改變著我們的生活,不知不覺,淘寶、支付寶已經(jīng)改變了很多人的生活。在我看來,這些都是很偉大的創(chuàng)造,偉大的“技術(shù)”,創(chuàng)造的社會效益顯而易見。

    5、脫離商業(yè)的技術(shù)不存在,計(jì)算炮彈軌跡的需求誕生了計(jì)算機(jī),美國國防部催生了互聯(lián)網(wǎng),網(wǎng)絡(luò)購物的需要誕生了淘寶,在不健全的信用社會網(wǎng)絡(luò)交易的需要誕生了支付寶。

    6、關(guān)于個人崇拜,你是成年人,你有自己的價值觀,你有自己的世界觀,如果你那么容易被人忽悠,那也是活該。

    7、如果哪天公司免費(fèi)發(fā)淘公仔,我也去搶啊,哦,搶過一回口碑卡。

    8、以偏概全,會掩蓋了很多人的努力工作。
     



    posted @ 2010-11-02 13:25 dennis 閱讀(2042) | 評論 (6)編輯 收藏

       
        javaeye的一個帖子介紹一道面試題,取數(shù)組的最大元素和前n個大元素,取最大元素很簡單,遍歷即可。取前N大元素,可以利用排序,最簡單的實(shí)現(xiàn):

        public static int[] findTopNValues(int[] anyOldOrderValues, int n) {
            Arrays.sort(anyOldOrderValues);
            
    int[] result = new int[n];
            System.arraycopy(anyOldOrderValues, anyOldOrderValues.length 
    - n,
                    result, 
    0, n);
            
    return result;
        }
       
         Arrays.sort(int[])使用的是快排,平均的時間復(fù)雜度是O( n lg(n)),在一般情況下已經(jīng)足夠好。那么有沒有平均情況下O(n)復(fù)雜度的算法?這個還是有的,這道題目其實(shí)是選擇算法的變形,選擇一個數(shù)組中的第n大元素,可以采用類似快排的方式劃分?jǐn)?shù)組,然后只要在一個子段做遞歸查找就可以,平均狀況下可以做到O(n)的時間復(fù)雜度,對于這道題來說稍微變形下算法即可解決:

        /**
         * 求數(shù)組的前n個元素
         * 
         * 
    @param anyOldOrderValues
         * 
    @param n
         * 
    @return
         
    */
        
    public static int[] findTopNValues(int[] anyOldOrderValues, int n) {
            
    int[] result = new int[n];
            findTopNValues(anyOldOrderValues, 
    0, anyOldOrderValues.length - 1, n,
                    n, result);
            
    return result;
        }

        
    public static final void findTopNValues(int[] a, int p, int r, int i,
                
    int n, int[] result) {
            
    // 全部取到,直接返回
            if (i == 0)
                
    return;
            
    // 只剩一個元素,拷貝到目標(biāo)數(shù)組
            if (p == r) {
                System.arraycopy(a, p, result, n 
    - i, i);
                
    return;
            }
            
    int len = r - p + 1;
            
    if (i > len || i < 0)
                
    throw new IllegalArgumentException();
            
    // if (len < 7) {
            
    // Arrays.sort(a, p, r+1);
            
    // System.arraycopy(a, r - i+1 , result, n - i, i);
            
    // return;
            
    // }

            
    // 劃分
            int q = medPartition(a, p, r);
            
    // 計(jì)算右子段長度
            int k = r - q + 1;
            
    // 右子段長度恰好等于i
            if (i == k) {
                
    // 拷貝右子段到結(jié)果數(shù)組,返回
                System.arraycopy(a, q, result, n - i, i);
                
    return;
            } 
    else if (k > i) {
                
    // 右子段比i長,遞歸到右子段求前i個元素
                findTopNValues(a, q + 1, r, i, n, result);
            } 
    else {
                
    // 右子段比i短,拷貝右子段到結(jié)果數(shù)組,遞歸左子段求前i-k個元素
                System.arraycopy(a, q, result, n - i, k);
                findTopNValues(a, p, q 
    - 1, i - k, n, result);
            }
        }

        
    public static int medPartition(int x[], int p, int r) {
            
    int len = r - p + 1;
            
    int m = p + (len >> 1);
            
    if (len > 7) {
                
    int l = p;
                
    int n = r;
                
    if (len > 40) { // Big arrays, pseudomedian of 9
                    int s = len / 8;
                    l 
    = med3(x, l, l + s, l + 2 * s);
                    m 
    = med3(x, m - s, m, m + s);
                    n 
    = med3(x, n - 2 * s, n - s, n);
                }
                m 
    = med3(x, l, m, n); // Mid-size, med of 3
            }
            
    if (m != r) {
                
    int temp = x[m];
                x[m] 
    = x[r];
                x[r] 
    = temp;
            }
            
    return partition(x, p, r);
        }

        
    private static int med3(int x[], int a, int b, int c) {
            
    return x[a] < x[b] ? (x[b] < x[c] ? b : x[a] < x[c] ? c : a)
                    : x[b] 
    > x[c] ? b : x[a] > x[c] ? c : a;
        }

        
    public static void swap(int[] a, int i, int j) {
            
    int temp = a[i];
            a[i] 
    = a[j];
            a[j] 
    = temp;
        }

        
    public static int partition(int a[], int p, int r) {
            
    int x = a[r];
            
    int m = p - 1;
            
    int j = r;
            
    while (true) {
                
    do {
                    j
    --;
                } 
    while (j>=p&&a[j] > x);
                
    do {
                    m
    ++;
                } 
    while (a[m] < x);
                
                
    if (j < m)
                    
    break;
                swap(a, m, j);
            }
            swap(a, r, j 
    + 1);
            
    return j + 1;
        }
     選擇算法還有最壞情況下O(n)復(fù)雜度的實(shí)現(xiàn),有興趣可以讀算法導(dǎo)論和維基百科。題外,我測試了下這兩個實(shí)現(xiàn),在我的機(jī)器上大概有2倍多的差距,還是很明顯。

    posted @ 2010-10-28 10:53 dennis 閱讀(3152) | 評論 (8)編輯 收藏

    僅列出標(biāo)題
    共56頁: First 上一頁 4 5 6 7 8 9 10 11 12 下一頁 Last 
    主站蜘蛛池模板: 国产真人无码作爱视频免费| 亚洲资源最新版在线观看| 亚洲日韩国产精品乱| 日韩一区二区免费视频| 好吊妞在线新免费视频| 最近免费中文字幕视频高清在线看 | 永久在线免费观看| 日本xxxx色视频在线观看免费| 成人性生交大片免费看好| a级毛片在线免费观看| 国产精品内射视频免费| 国产精品免费视频观看拍拍| 中国人免费观看高清在线观看二区| 一道本不卡免费视频| 成人A毛片免费观看网站| 中文字幕免费在线看| 国产成人一区二区三区视频免费| 在线免费观看h片| 久久久久久久久久国产精品免费 | 国产精品亚洲四区在线观看| 久久亚洲国产最新网站| 亚洲日韩看片无码电影| 久久久久亚洲AV无码去区首 | 亚洲日韩精品一区二区三区无码| 在线亚洲精品自拍| 亚洲妇熟XXXX妇色黄| 亚洲的天堂av无码| 亚洲综合无码一区二区痴汉| 国产亚洲美女精品久久| caoporm超免费公开视频| 大地资源网高清在线观看免费| 久久精品一区二区免费看| aⅴ免费在线观看| 国外成人免费高清激情视频| 免费大黄网站在线观| 国产亚洲日韩在线三区| 亚洲午夜免费视频| 亚洲午夜一区二区三区| 成人国产网站v片免费观看| 在线毛片片免费观看| 国产va精品免费观看|