莊周夢蝶

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


              寫這篇文章的想法產生在昨天晚上讀《面向對象分析與設計》的時候,我漸漸發現我們這個小組不知不覺地貫徹了很多非常有價值的實踐經驗,這些點點滴滴都對我們的最終的產品質量產生了或大或小的影響,保證我們的系統不會出現重大的故障。我想有必要將這些“隱性知識”稍微總結一下,以供參考和記錄。

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

             下面分階段總結下我們的一些實踐經驗。

          一、分析和設計階段

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

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

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

          二、構造階段

          1、整個系統已經有一個關鍵的抽象機制,針對我們的服務器有一個核心的pipeline機制,針對我們的客戶端,有一個核心的發送消息流程。將所有的功能模塊組織在這個關鍵機制周圍,形成一個強有力的整體。

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

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

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

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

          6、每個功能完成之后,立即發起review,請同事和你一起復審代碼。復審代碼的作用不僅是發現bug,改良設計,也是一個知識交流的最佳途徑。我們經常能通過代碼審查發現一些設計上的缺陷,以及功能實現上的BUG。我們團隊應該說是非常看重代碼審查的作用。

          7、使用findbugs和clover等工具,分析代碼質量并改進。

          8、在發布之前,做一次集中的代碼review,每個人介紹下自己的功能實現代碼和設計,一般我們會申請一個會議室和投影儀,并邀請團隊之外的人加入review。

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

          三、發布和總結
          1、發布之前,最好讓使用我們系統的用戶使用新版本做一個回歸測試,一方面是測試兼容性,一方面也可以及早發現BUG。

          2、我們的發布流程:線下、beta、線上。每個階段通常都持續一到兩周,才會進行到下一階段。并且是從相對不重要的系統,到關鍵系統的順序進行發布。

          3、發布之后,通過日志、運行時監控、用戶反饋等方式收集系統運行狀況,發現BUG,修正BUG,補充測試,測試通過,重新發布。

          4、每個版本發布后,需要總結下本次發布過程中遇到的所有BUG以及經驗教訓,并提出可能的改進建議。

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

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


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

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

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

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

                不僅如此,xmemcached 1.3還將引入standby node的概念,你可以設置某個memached節點的備份節點,當這個節點掛掉的時候會將請求轉發給這個備份節點,不會簡單地拋出異常,也不會轉發給其他節點。要使用standby node,必須首先設置使用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);

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


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





            

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

              最近沒更新blog,因為我忙著寫程序,玩android。入手了一臺moto xt701,下單后悔來想換里程碑,但是京東打包太快,客服不讓換,這個很奇怪,我想用更多錢買東西反而被拒絕,看來京東處理太快也不一定是優點

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






            

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

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

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


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

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

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

             項目地址:http://code.google.com/p/hs4j/

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

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

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

             假設HandlerSocket運行在本地的9999端口,默認的9998是只讀的,9999才是允許讀和寫。HSClient是線程安全的。

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

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

             其中db是數據庫名,table是表名,"PRIMARY"表示使用主鍵索引,columns是一個字符串數組代表你要查詢的字段名稱。這里沒有指定indexid,默認會產生一個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那樣去操作結果集。當然我的簡單實現并不符合JDBC規范,只實現了最常見的一些方法,如getStrng、getLong等。find(keys)方法默認使用的op是"="。其他重載方法可以設置其他類型的op,統一封裝為枚舉類型FindOperator。

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

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

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

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

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

             在open index的時候,會在連接池里所有的連接上都open。并且在連接因為意外情況(如網絡錯誤)斷開的時候,HS4J會自動重連,并在重連成功的情況下自動發送已經open的index,保證應用的操作不受重連影響。

              因為HS4J是我在兩天內寫就的一個東西,可能還有不少隱藏的bug,并且HandlerSocket本身也是個新東西,如果有什么問題或者改進建議,隨時歡迎告訴我,多謝。


            
            

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


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





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


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

              Kilim默認還提供了一個簡易Http Server框架,但是沒有提供Http Client的實現,我的另一個改進是提供了一個簡易的Http Client實現,也是利用Ragel做協議解析,一個簡單的使用例子如下:
          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調用已經足夠。我嘗試在一個項目里使用這個HttpClient去替代java默認的HttpURLConnection,效率有部分提升,但是同時由于大量協程存在占用了很大部分的內存,給GC也帶來了不小的壓力。

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

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

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

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

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

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

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

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

          四、模式與設計

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

          五、方法論
          1、《敏捷軟件開發》
          2、《測試驅動開發》,你不一定要TDD,但是你一定要學會做單元測試。
          3、《Agile Java》,也可以作為java入門讀物。
          4、《快速軟件開發》
          5、《面向對象分析與設計》,OO設計必讀。
          6、《Unix編程藝術》,打開你的眼界。

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




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


              我最近在實現一個基于Kilim的HttpClient,在處理響應body特別大的情形下遇到了kilim的一個BUG,有必要記錄下。
              問題是這樣,Kilim將連接封裝為EndPoint對象,EndPoint有個方法fill用于從管道讀數據到緩沖區,并且可以指定希望至少讀到多少個字節(atLeastN)才返回。那么在進入此方法的時候會判斷緩沖區是否有足夠空間容納atLeastN個字節,如果沒有,則創建一個更大的緩沖區,并將“老”的緩沖區的數據拷貝到新緩沖區,這部分代碼是這樣實現:
          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就出現在這段代碼里。這段代碼的邏輯很簡單,先是創建一個新的更大的緩沖區,然后將老的緩沖區的數據put到新的緩沖區,在put之前調用rewind方法將老的緩沖區的position設置為0。查看rewind干了什么:
           public final Buffer rewind() {
              position 
          = 0;
              mark 
          = -1;
              
          return this;
              }

              僅僅是將position設置為0,并讓mark失效。position指向下一個讀或者寫的位置,這里在寫入到新緩沖區之前確實需要將position設置為0,以便寫入從老的緩沖區第一個位置開始。問題是什么?問題是position僅僅指定了下一個讀取數據的位置,卻沒有指定有效數據的大小,換句話說,沒有指定老的緩沖區的limit。因此這里造成的后果是老的緩沖區整個被寫入到新的老緩沖區,包括有效數據和無效數據,默認情況下緩沖區的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寫入一個整數99,然后創建newBuf并寫入old數據,并再寫入一個整數100,最后從newBuf讀數據。本來我們預期只應該讀到兩個整數99和100,但是中間卻插入一個0,輸出如下:

          12
          99
          0
          100

              12表示緩沖區可讀的數據,本來應該是8個字節,卻多了4個字節的無效數據。

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


              修改上面的測試程序,符合我們的預期了:
                  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

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

             而flip用于緩沖區發送或者讀取之前,也就是將緩沖區設置為等待傳出狀態。

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

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

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

          2、其次,文中作者提到的yahoo的優秀點,在淘寶我都發現了傳承,我估計是學習yahoo中國的,比如發布流程、比如知識庫、比如很有特色的技術大學培訓、比如鼓勵創建自動化工具等等,我不知道作者有沒有在阿里集團的子公司待過,或者來淘寶待過,如果來過呆過,我想不會沒看見。

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

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

          5、脫離商業的技術不存在,計算炮彈軌跡的需求誕生了計算機,美國國防部催生了互聯網,網絡購物的需要誕生了淘寶,在不健全的信用社會網絡交易的需要誕生了支付寶。

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

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

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



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

             
              javaeye的一個帖子介紹一道面試題,取數組的最大元素和前n個大元素,取最大元素很簡單,遍歷即可。取前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[])使用的是快排,平均的時間復雜度是O( n lg(n)),在一般情況下已經足夠好。那么有沒有平均情況下O(n)復雜度的算法?這個還是有的,這道題目其實是選擇算法的變形,選擇一個數組中的第n大元素,可以采用類似快排的方式劃分數組,然后只要在一個子段做遞歸查找就可以,平均狀況下可以做到O(n)的時間復雜度,對于這道題來說稍微變形下算法即可解決:

              /**
               * 求數組的前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;
                  
          // 只剩一個元素,拷貝到目標數組
                  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);
                  
          // 計算右子段長度
                  int k = r - q + 1;
                  
          // 右子段長度恰好等于i
                  if (i == k) {
                      
          // 拷貝右子段到結果數組,返回
                      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短,拷貝右子段到結果數組,遞歸左子段求前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)復雜度的實現,有興趣可以讀算法導論和維基百科。題外,我測試了下這兩個實現,在我的機器上大概有2倍多的差距,還是很明顯。

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

          僅列出標題
          共56頁: First 上一頁 4 5 6 7 8 9 10 11 12 下一頁 Last 
          主站蜘蛛池模板: 阿拉善左旗| 汉中市| 江都市| 贵南县| 牡丹江市| 商水县| 焉耆| 仁布县| 临朐县| 祁东县| 陇南市| 陆良县| 塔河县| 房山区| 略阳县| 遂平县| 毕节市| 潮州市| 十堰市| 灵台县| 灌云县| 库车县| 汕尾市| 临安市| 辽宁省| 龙川县| 新竹县| 博爱县| 澄江县| 新巴尔虎左旗| 扶沟县| 临汾市| 马鞍山市| 福泉市| 深水埗区| 启东市| 于都县| 湖南省| 南乐县| 重庆市| 丹阳市|