Java新手的跌倒日記-樹狀文件結構產生

 Mon, 19 Nov 2007 17:35:49 +0800

為了練習,自己寫了csv -> xml(office 2003)的轉碼程式,中途跌倒了很多次,主要原因都是自訂的node類別。

原先的簡單想法是,每個node都會有一個render方法,在這個render方法裡面遞迴地呼叫子node的render方法,把整個文件產生出來。寫法很簡單,結構很漂亮,但是.....跑得好慢。

拿來測試的csv有1200筆資料,每一筆資料有五個欄位。每個欄位用一個textnode來代表,然後用data跟cell node包起來,每筆資料用row node包起來,外面在加上table,worksheet,workbook三個node。估計一下,這樣得要在系統裡面產生3*5*1200+1200+3,19203個node物件。

估計一下,有幾個重要的瓶頸:

  1. 因為用遞迴的方式呼叫render方法,過程中會遍歷所有的節點,所以.....這些node物件佔用的記憶體都不會被釋放,直到根節點的render方法執行完畢。
  2. 跑完所有的render後,才產生完整的檔案內容字串,然後寫入檔案。只寫了一次,所以即使使用BufferedWriter也不能增加效率。
  3. 集中釋放這19203個物件的Garbage Collection動作,非常花時間。

看起來只要解決兩個問題,就可以大幅提高效率:

  1. 分批產生node物件然後釋放,避免一次釋放。
  2. 分批寫入檔案,使用BufferedWriter提高寫入的效率。

我想到的方法有兩個部份:

  1. 把render拆成三個方法,就是renderHead負責開始的tag,renderFoot負責結束的tag。呼叫子節點的render方法部份拆出成renderBody,但是並不實際呼叫。然後在render方法裡面依序呼叫這三個,測試看可行否。
  2. 分析了一下xml的文件結構,看起來可以用stack來處理這個結構,每個節點在進入子節點前,先呼叫renderHead,然後把自己push進堆疊。接著子節點也依照父節點的步驟順序執行。所有子節點訪問完了,pop出來執行renderFoot,然後用null釋放。

經過這兩個步驟,文件產生的過程已經變成循序的了,所以就直接把renderHead與renderFoot的結果直接寫進BufferedWriter。

跑了一下:恩.....速度快了非常多,應該有三十倍左用吧?記憶體也用的非常少,大概只有十分之一。(在main裡面用System.currentTimeMillis()來評估開始結束的時間差,用-verbosegc選項觀察heap使用的峰值來評估記憶體使用)

感想:遞迴的結構是很漂亮,但是執行起來效率實在是.....


2007-11-20 7:56 AM 修改

記憶體耗用的比較,沒有扣掉空的main似乎不夠準確。我另外寫了一個空的main,裡面用System.gc()強制做GC,然後用-verbosegc選項來跑,紀錄gc時的heap峰值。上面記憶體使用如果扣掉空的main,差距可以到18倍左右。


2007-11-20 8:59 AM 修改

嗚嗚,看錯欄位了,記憶體耗用沒差那麼多,大概是六到七倍。不過這樣估計應該不是很準,用遞迴的方式node建立完時,用到的記憶體大概是render結束時的大約1/2。不過產生的檔案很大,有584KB。這也用了不少記憶體。


2007-11-22 9:10 AM 修改

仔細考慮的一下我用的方法,其實要把csv的欄位填入xml時,xml的架構都沒有改變。所以其實可以先產生各個節點,然後再依照順序呼叫各個節點的renderHead、renderFoot方法,這樣可以重複利用節點物件,根本不用再重複產生,也不必用stack結構來存放。