2013年9月10日 星期二

[ ML In Action ] Unsupervised learning : Efficiently finding frequent itemsets with FP-growth

Preface: 
在上一章 Association analysis with the Apriori algorithm 我們使用 Apriori 演算法從 transaction list 找到 data 間的關係, 常見的應用是便利商店可以用來判斷當你買了商品 A 後接著會買其他商品的可能組合. 而在這個過程中我們第一步要找的是所謂的 frequent item set, 並透過設定 minimum support 來 filter 不常見的 item(s). 而在 Apriori 是使用窮舉法找出所有可能的 data set 組合, 雖然有利用 pruning 的技巧, 但是效率仍然不是很理想, 在處理大量資料時可能會遇到處理速度的瓶頸. 這邊要介紹另一種演算法, 讓你能更快速的從 transaction set 中找出 frequent item set. 該演算法需要建立一種特殊的資料結構稱為 FP-Tree (FP means Frequent Pattern) 而該演算法為 FP-growth algorithm. 

FP-trees: an efficient way to encode a dataset: 
在使用 FP-growth algorithm 前必須先建立 FP-Tree, 接著使用 FP-Tree 來 mining 出 frequent item set. 該演算法有以下特性: 
Pros: Usually faster than Apriori.
Cons: Difficult to implement; certain datasets degrade the performance.
Works with: Nominal values.

首先我們來看一個簡單的範例, 來了解 FP-Tree 是如何建立. 假設你有如下的 transaction list: 
 

在使用 transaction list 前, 我們要做一些前置處理. 就如在 Apriori 中處理 frequent item set 一樣, 我們會根據 minimum support 移除不常出現的 item (這邊 minimum support=3); 接著將 transaction 紀錄依據出現頻率從高到低進行 sorting. 處理完結果如下: 
 

接著我們就要用處理過後的 transaction list 來建立 FP-Tree 時. 在 FP-Tree 在最上層的 root 是空集合的符號. 底下是前兩筆 transaction 建立 FP-Tree 的過程: 
 

在 item 旁邊的數字說明該 item 出現的頻率 (z:1 說明 'z' 目前頻率為 1), 可能會隨著加入越來越多的 transaction 而增加. 另外為了迅速找到某個 item 在 FP-Tree 中的位置, 我們會建立一個 Header Table (鍵為 item, 值為該 item 在 FP-Tree 中的位置), 而每個 Node 都會有一個 link 連到同樣 item 的下一個 Node 而形成類似 linked list 的結構, 完整的 FP-Tree 建立結果長相如下: 
 
Figure 12.2 FP-tree with header table. The header table serves as a starting point to find similar items. 

下一個部份我們要來看如何透過代碼來建立 FP-Tree 並利用 FP-Tree 來 mining frequent items. 在開始 coding 前, 我們定義下面的類別作為 FP-Tree 的 node 以記載相關資料: 
  1. class treeNode:  
  2.     def __init__(self, nameValue, numOccur, parentNode):  
  3.         self.name = nameValue       # item name  
  4.         self.count = numOccur       # frequency   
  5.         self.nodeLink = None        # pointer to next node with same item name  
  6.         self.parent = parentNode    # needs to be updated  
  7.         self.children = {}          # next node according to sorting transaction set  
  8.       
  9.     def inc(self, numOccur):  
  10.         self.count += numOccur  
  11.           
  12.     def disp(self, ind=1):  
  13.         print '  '*ind, self.name, ' ', self.count  
  14.         for child in self.children.values():  
  15.             child.disp(ind+1)  
底下我們要先來看看從 transaction set 建立 FP-Tree 的部分代碼: 
- Listing 12.2 FP-tree creation code 
  1. """Create tree based on given .  
  2.     Args:  
  3.         dataSet: Input transaction set.  
  4.         minSup: Used to filter out infrequent item(s).  
  5.           
  6.     Returns:  
  7.         retTree: FP-Tree(treeNode) or None if no frequent item  
  8.         headerTable: Header table of FP-Tree or None if no frequent item  
  9.                      headerTable[item] = (frequency,)  
  10.           
  11.     Raises:  
  12.         None  
  13. """  
  14. def createTree(dataSet, minSup=1): #create FP-tree from dataset but don't mine  
  15.     headerTable = {}  
  16.     #go over dataSet twice  
  17.     for trans in dataSet:#first pass counts frequency of occurance  
  18.         for item in trans:  
  19.             headerTable[item] = headerTable.get(item, 0) + dataSet[trans]  
  20.     for k in headerTable.keys():  #remove items not meeting minSup  
  21.         if headerTable[k] < minSup:   
  22.             del(headerTable[k])  
  23.     freqItemSet = set(headerTable.keys())  
  24.     #print 'freqItemSet: ',freqItemSet  
  25.     if len(freqItemSet) == 0: return None, None  #if no items meet min support -->get out  
  26.     for k in headerTable:  
  27.         headerTable[k] = [headerTable[k], None] #reformat headerTable to use Node link   
  28.     #print 'headerTable: ',headerTable  
  29.     retTree = treeNode('Null Set', 1, None) #create tree  
  30.     for tranSet, count in dataSet.items():  #go through dataset 2nd time  
  31.         localD = {}  
  32.         for item in tranSet:  #put transaction items in order  
  33.             if item in freqItemSet:  
  34.                 localD[item] = headerTable[item][0]  
  35.         if len(localD) > 0:  
  36.             orderedItems = [v[0] for v in sorted(localD.items(), key=lambda p: p[1], reverse=True)]  
  37.             updateTree(orderedItems, retTree, headerTable, count)#populate tree with ordered freq itemset  
  38.     return retTree, headerTable #return tree and header table  
  39.   
  40. """ Recursively update the FP-Tree  
  41.     Args:  
  42.         items: Ordered frequent items  
  43.         inTree: FP-Tree node  
  44.         headerTable: Header table of FP-Tree  
  45.         count: Count of exist same transaction of input items.  
  46.           
  47.     Returns:  
  48.         None  
  49.       
  50.     Raises:  
  51.         None  
  52. """  
  53. def updateTree(items, inTree, headerTable, count):  
  54.     if items[0] in inTree.children:#check if orderedItems[0] in retTree.children  
  55.         inTree.children[items[0]].inc(count) #incrament count  
  56.     else:   #add items[0] to inTree.children  
  57.         inTree.children[items[0]] = treeNode(items[0], count, inTree)  
  58.         if headerTable[items[0]][1] == None: #update header table   
  59.             headerTable[items[0]][1] = inTree.children[items[0]]  
  60.         else:  
  61.             updateHeader(headerTable[items[0]][1], inTree.children[items[0]])  
  62.     if len(items) > 1:#call updateTree() with remaining ordered items  
  63.         updateTree(items[1::], inTree.children[items[0]], headerTable, count)  
  64.           
  65. def updateHeader(nodeToTest, targetNode):   #this version does not use recursion  
  66.     while (nodeToTest.nodeLink != None):    #Do not use recursion to traverse a linked list!  
  67.         nodeToTest = nodeToTest.nodeLink  
  68.     nodeToTest.nodeLink = targetNode  
底下是產生測試 transaction set 的代碼: 
- Listing 12.3 Simple dataset and data wrapper 
  1. """Create testing transaction set  
  2.     Args:  
  3.         None  
  4.           
  5.     Returns:  
  6.         simpDat: Transaction set  
  7.           
  8.     Raises:  
  9.         None  
  10. """  
  11. def loadSimpDat():  
  12.     simpDat = [['r', 'z', 'h', 'j', 'p'],  
  13.                ['z', 'y', 'x', 'w', 'v', 'u', 't', 's'],  
  14.                ['z'],  
  15.                ['r', 'x', 'n', 'o', 's'],  
  16.                ['y', 'r', 'x', 'z', 'q', 't', 'p'],  
  17.                ['y', 'z', 'x', 'e', 'q', 's', 't', 'm']]  
  18.     return simpDat  
  19.   
  20. """Build up directory to contains (transaction->frequency)  
  21.     Args:  
  22.         dataSet: Transaction set created by API:loadSimpDat()  
  23.           
  24.     Return:  
  25.         retDict:  
  26.           
  27.     Raise:  
  28.         None  
  29. """  
  30. def createInitSet(dataSet):  
  31.     retDict = {}  
  32.     for trans in dataSet:  
  33.         retDict[frozenset(trans)] = 1  
  34.     return retDict  
接著假設代碼撰寫在 "fpGrowth.py", 我們可以如下測試 FP-Tree 的建立過程: 
 

Mining frequent items from an FP-tree: 
當你建立 FP-Tree, 接下來要 mining 出 frequent items 就相當 straight forward, 以下是後續的三個動作: 
1. Get conditional pattern bases from the FP-tree.
2. From the conditional pattern base, construct a conditional FP-tree.
3. Recursively repeat steps 1 and 2 on until the tree contains a single item.

那什麼是 "conditional pattern bases"? 你可以想像成剛剛產生的 frequent item set 中每個 item 從剛剛建立的 FP-Tree 可以走出所有可能的 path, 而該 path 的最後一個 item 為 target item. 舉例來說, 當 target item="z" 時, 從 FP-Tree 能走到 "z" 的所有走法只有 {Φ->z}5 (5 指的是頻率): 
 

如果 target item="r", 則走法有 {Φ->z->r}1 , {Φ->z->x->y->r}1, {Φ->x->s->r}1: 
 

如果拿掉 path 的頭尾, 底下整理出所有 frequent item set 的 "conditional pattern bases": 
 

而地下是找出 "conditional pattern bases" 的代碼, 而在找的過程中代碼利用 Header table 指向每一個 frequent item, 並利用其 nodeLink 屬性指向下一個相同 item 的 Node; 並利用 Node 上面的的 parentNode 屬性往上走直到 root (parentNode=None): 
- Listing 12.4 A function to find all paths ending with a given item 
  1. """Ascends from the left node to root and store the path to .  
  2.     Args:  
  3.         leafNode: Node of PF-Tree  
  4.         prefixPath: list to store the traversal path  
  5.           
  6.     Returns:  
  7.         None  
  8.           
  9.     Raises:  
  10.         None  
  11. """          
  12. def ascendTree(leafNode, prefixPath): #ascends from leaf node to root  
  13.     if leafNode.parent != None:  
  14.         prefixPath.append(leafNode.name)  
  15.         ascendTree(leafNode.parent, prefixPath)  
  16.           
  17. """Find prefix path according to given FP-Tree node as   
  18.     Args:  
  19.         basePat: ?  
  20.         treeNode: Target FP-Tree node.  
  21.           
  22.     Return:  
  23.         condPats: The directory to hold {conditional path->count}  
  24.           
  25.     Raises:  
  26.         None  
  27. """  
  28. def findPrefixPath(basePat, treeNode): #treeNode comes from header table  
  29.     condPats = {}  
  30.     while treeNode != None:  
  31.         prefixPath = []  
  32.         ascendTree(treeNode, prefixPath)  
  33.         if len(prefixPath) > 1:   
  34.             condPats[frozenset(prefixPath[1:])] = treeNode.count  
  35.         treeNode = treeNode.nodeLink  
  36.     return condPats  
接著底下是上面代碼的使用範例: 
 

接下來利用這些找到的 "conditional pattern bases" 當作 transaction 在建立 "conditional FP-trees". 以 target item='t' 為範例, 底下是建立的過程: 
 
(item 's' 與 'r' 沒有出現在 "conditional FP-trees" 是因為它們不滿足 minimum support=3 的限制) 

如此第三步只是重複 一, 二步. 因此我們只要利用 recursive 的方法便可以完成, 而 stop condition 為當 "conditional FP-trees" 為 None 時並終止 recursive. 底下為 mining 過程代碼: 
- Listing 12.5 The mineTree function recursively finds frequent itemsets. 
  1. """Recursively finds frequent itemsets.  
  2.     Args:  
  3.         inTree: FP-Tree  
  4.         headerTable: Header table of FP-Tree  
  5.         preFix: Prefix paths  
  6.         freqItemList: Frequent item list (Final result)  
  7.           
  8.     Returns:  
  9.         None  
  10.           
  11.     Raises:  
  12.         None  
  13. """  
  14. def mineTree(inTree, headerTable, minSup, preFix, freqItemList):  
  15.     bigL = [v[0] for v in sorted(headerTable.items(), key=lambda p: p[1])]#(sort header table)  
  16.     for basePat in bigL:  #start from bottom of header table  
  17.         newFreqSet = preFix.copy()  
  18.         newFreqSet.add(basePat)  
  19.         #print 'finalFrequent Item: ',newFreqSet    #append to set  
  20.         freqItemList.append(newFreqSet)  
  21.         condPattBases = findPrefixPath(basePat, headerTable[basePat][1])  
  22.         #print 'condPattBases :',basePat, condPattBases  
  23.         #2. construct cond FP-tree from cond. pattern base  
  24.         myCondTree, myHead = createTree(condPattBases, minSup)  
  25.         #print 'head from conditional tree: ', myHead  
  26.         if myHead != None: #3. mine cond. FP-tree  
  27.             #print 'conditional tree for: ',newFreqSet  
  28.             #myCondTree.disp(1)              
  29.             mineTree(myCondTree, myHead, minSup, newFreqSet, freqItemList)  
最後的使用範例也相當簡單, 而上面完整代碼可到 這裡下載: 
 

Supplement: 
* [ Python 範例代碼 ] Sorting directory into item key list according to the item value

2013年9月9日 星期一

[ Python 範例代碼 ] Sorting directory into item key list according to the item value

Preface: 
考慮你有一個 directory aset 如下: 
>>> aset = {}
>>> aset['a']=5
>>> aset['b']=2
>>> aset['c']=4
>>> aset['d']=1
>>> aset['e']=7
>>> aset['f']=6
>>> aset['g']=3
>>> aset['h']=2
>>> aset
{'a': 5, 'c': 4, 'b': 2, 'e': 7, 'd': 1, 'g': 3, 'f': 6, 'h': 2}

你希望根據每個 item 的 value 值進行 sorting, 並將 sorting 完過後的 item 的 key 值 存成一個 list. 則可以參考下面範例代碼. 

Sample Code: 
在下面代碼中, 同時結合使用 for comprehension 與 lambda 語法完成上面需求: 
>>> orderedItems = [v[0] for v in sorted(aset.items(), key=lambda p:p[1], reverse=True)]
>>> orderedItems
['e', 'f', 'a', 'c', 'g', 'b', 'h', 'd'] # 因為 'e' 的 value=7 為最大, 故排在最前面; 反之因為 'd' 的 value=1 最小, 故排在最後面.

Supplement: 
* Python built-in function : sorted

[ Java 文章收集 ] 多核平台JAVA優化

來源自 這裡
Preface:
現在多核 CPU 是主流。利用多核技術,可以有效發揮硬件的能力,提升吞吐量,對於 Java 程序,可以實現並發垃圾收集。但是 Java 利用多核技術也帶來了一些問題,主要是多線程共享內存引起了。目前內存和 CPU 之間的帶寬是一個主要瓶頸,每個核可以獨享一部分高速緩存,可以提高性能。JVM 是利用操作系統的”輕量級進程”實現線程,所以線程每操作一次共享內存,都無法在高速緩存中命中,是一次開銷較大的系統調用。所以區別於普通的優化,針對多核平台,需要進行一些特殊的優化.

線程數要大於等於核數:
如果使用多線程,只有運行的線程數比核數大,才有可能榨干 CPU 資源,否則會有若干核閑置。要注意的是,如果線程數目太多,就會佔用過多內存,導致性能不升反降。JVM 的垃圾回收也是需要線程的,所以這里的線程數包含 JVM 自己的線程.

盡量減少共享數據寫操作:
每個線程有自己的工作內存,在這個區域內,系統可以毫無顧忌的優化,如果去讀共享內存區域,性能也不會下降。但是一旦線程想寫共享內存(使用 volatile 關鍵字),就會插入很多內存屏障操作(Memory Barrier 或者 Memory Fence)指令,保証處理器不亂序執行。相比寫本地線程自有的變量,性能下降很多。處理方法是盡量減少共享數據,這樣也符合”數據耦合”的設計原則。

使用 synchronize 關鍵字:
在 Java1.5 中,synchronize 是性能低效的。因為這是一個重量級操作,需要調用操作接口,導致有可能加鎖消耗的系統時間比加鎖以外的操作還多。相比之下使用 Java 提供的 Lock 對象,性能更高一些。但是到了 Java1.6,發生了變化。synchronize 在語義上很清晰,可以進行很多優化,有適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在 Java1.6 上 synchronize 的性能並不比 Lock 差。官方也表示,他們也更支持 synchronize,在未來的版本中還有優化餘地.

使用樂觀策略:
傳統的同步並發策略是悲觀的。表現語義為:多線程操作一個對象的時候,總覺得會有兩個線程在同時操作,所以需要鎖起來。樂觀策略是,假設平時就一個線程訪問,當出現了沖突的時候,再重試。這樣更高效一些。Java 的 AtomicInteger 就是使用了這個策略!

使用線程本地變量(ThreadLocal)
使用 ThreadLocal 可以生成線程本地對象的副本,不會和其他線程共享。當該線程終止的時候,其本地變量可以全部回收。

類中 Field 的排序:
可以將一個類會頻繁訪問到的幾個 field 放在一起,這樣他們就有更多的可能性被一起加入高速緩存。同時最好把他們放在頭部。基本變量和引用變量不要交錯排放!

批量處理數組:
現在處理器可以用一條指令來處理一個數組中的多條記錄,例如可以同時向一個 byte 數組中讀或者寫 store 記錄。所以要盡量使用 System.arraycopy() 這樣的批量接口,而不是自己操作數組.

底下為 JVM 優化 的內容...
啟用大內存頁:
現在一個操作系統默認頁是4K。如果你的 heap 是 4GB,就意味著要執行 1024*1024 次分配操作。所以最好能把頁調大。這個配額設計操作系統,單改 Jvm 是不行的。Linux 上的配置有點復雜,不詳述. 在 Java1.6 中 UseLargePages 是默認開啟的,LasrgePageSzieInBytes 被設置成了4M。筆者看到一些情況下配置成了128MB,在官方的性能測試中更是配置到256MB. (可以參考 Java Support for Large Memory Pages)

啟用壓縮指針:
Java 的64的性能比32慢,原因是因為其指針由32位擴展到64位,雖然尋址空間從4GB 擴大到 256 TB,但導致性能的下降,並佔用了更多的內存。所以對指針進行壓縮。壓縮後的指針最多支持 32GB 內存,並且可以獲得 32位 JVM 的性能。在 JDK6 update 23 默認開啟了,之前的版本可以使用 -XX:+UseCompressedOops 來啟動配置。性能可以看這個評測,性能的提升是很可觀.

啟用 NUMA:
NUMA (Non-Uniform Memory Access) 是一個 CPU 的特性。SMP 架構下,CPU 的核是對稱,但是他們共享一條系統總線。所以 CPU 多了,總線就會成為瓶頸。在 NUMA 架構下,若干 CPU 組成一個組,組之間有點對點的通訊,相互獨立。啟動它可以提高性能。 NUMA 需要硬件,操作系統,JVM 同時啟用,才能啟用。Linux 可以用 numactl 來配置 numa,JVM 通過 -XX:+UseNUMA 來啟用。

激進優化特性:
在 Java1.6 中,激進優化(AggressiveOpts)是默認開啟的。激進優化是一般有一些下一個版本才會發布的優化選項。但是有可能造成不穩定。前段時間以訛傳訛的 JDK7的 Bug,就是開啟這個選項後測到的.

逃逸分析:
讓一個對象在一個方法內創建後,如果他傳遞出去,就可以稱為方法逃逸;如果傳遞到別的線程,成為線程逃逸。如果能知道一個對象沒有逃逸,就可以把它分配在棧而不是堆上,節約 GC 的時間。同時可以將這個對象拆散,直接使用其成員變量,有利於利用高速緩存。如果一個對象沒有線程逃逸,就可以取消其中一切同步操作,很大的提高性能.

但是逃逸分析是很有難度的,因為花了 cpu 去對一個對象去分析,要是他不逃逸,就無法優化,之前的分析血本無歸。所以不能使用復雜的算法,同時現在的 JVM 也沒有實現棧上分配。所以開啟之後,性能也可能下降. 可以使用 -XX:+DoEscapeAnalysis 來開啟逃逸分析。

高吞吐量 GC 配置:
對於高吞吐量,在年輕態可以使用 Parallel Scavenge,年老態可以使用 Parallel Old 垃圾收集器:
使用 -XX:+UseParallelOldGC 開啟
可以將 -XX:ParallelGCThreads 根據 CPU 的個數進行調整。可以是 CPU 數的1/2或者5/8 低延遲 GC 配置
對於低延遲的應用,在年輕態可以使用 ParNew,年老態可以使用 CMS 垃圾收集器。
可以使用 -XX:+UseConcMarkSweepGC 和 -XX:+UseParNewGC 打開。
可以將 -XX:ParallelGCThreads 根據 CPU 的個數進行調整。可以是 CPU 數的1/2或者5/8
可以調整 -XX:MaxTenuringThreshold (晉升年老代年齡)調高,默認是15.這樣可以減少年老代 GC 的壓力
可以 -XX:TargetSurvivorRatio,調整 Survivor 的佔用比率。默認50%.調高可以提供 Survivor 區的利用率
可以調整 -XX:SurvivorRatio,調整 Eden 和 Survivor 的比重。默認是8。這個比重越小,Survivor 越大,對象可以在年輕態呆更多時間。

Supplement:
* Java 理論與實踐: 正確使用Volatile 變量
Java 語言中的 volatile 變量可以被看作是一種 “程度較輕的synchronized ”;與 synchronized塊 相比,volatile 變量所需的編碼較少,並且運行時開銷也較少,但是它所能實現的功能也僅是 synchronized 的一部分。本文介紹了幾種有效使用volatile變量的模式,並強調了幾種不適合使用volatile變量的情形...

* 理解 ThreadLocal
* [ JVM 應用 ] 垃圾收集器與記憶體分配策略 - Part2


2013年9月4日 星期三

[ Java 代碼範本 ] HttpClient 4.x : 處理 meta refresh 的 redirection

Preface: 
使用 HttpClient 4.x 版本的 Http Get 如果要處理 status code 是 301(Moved Permanently)/302 (Found) 的 redirection 時, 需要透過方法 setRedirectStrategy() 修改DefaultRedirectStrategy 的行為, 底下為範例代碼: 
  1. DefaultHttpClient httpClient = new DefaultHttpClient();  
  2. httpClient.setRedirectStrategy(new DefaultRedirectStrategy() {                
  3.     @Override  
  4.     public HttpUriRequest getRedirect(HttpRequest request,  
  5.                   HttpResponse response,  
  6.                   HttpContext context)  
  7.                     throws ProtocolException{                 
  8.         HttpUriRequest redirect = super.getRedirect(request, response, context);  
  9.         redirtList.add(redirect.getURI().toString());                 
  10.         System.out.printf("\t[Test] Redirect to '%s'\n", redirect.getURI());  
  11.         return redirect;  
  12.     }  
  13.   
  14.     @Override  
  15.        public boolean isRedirected(HttpRequest request, HttpResponse resp, HttpContext context)  {                                
  16.         boolean isRedirect=false;                     
  17.            if(!isRedirect)  
  18.            {  
  19.             try {  
  20.                 isRedirect = super.isRedirected(request, resp, context);  
  21.             } catch (ProtocolException e) {                          
  22.                 e.printStackTrace();  
  23.             }  
  24.            }  
  25.            if(!isRedirect) {  
  26.                int responseCode = resp.getStatusLine().getStatusCode();  
  27.                if (responseCode == 301 || responseCode == 302) {  
  28.                    isRedirect = true;  
  29.                }  
  30.            }                                  
  31.            return isRedirect;                                         
  32.        }  
  33.       
  34.    });  
如此設定上面的 httpClient 在處理 HttpUriRequest 便會自動幫你 redirect 到最後的 page 去. 接著為了方便, 下面的函示都是之後會用到的, 首先是判斷頁面使用的 Charset: 
  1. public static String Charset(HttpResponse resp, String def)  
  2. {         
  3.     ContentType contentType = ContentType.getOrDefault(resp.getEntity());  
  4.     Charset charSet = contentType.getCharset();      
  5.     if(charSet!=null) return charSet.name();  
  6.     else return def;  
  7. }  
接著是透過 HttpResponse 取得頁面的內容: 
  1.    public synchronized static String GetPageBody(HttpResponse resp, String cs) throws IOException {  
  2.     //String cs = Charset(resp, "utf-8");  
  3.     Header contentEncoding = resp.getFirstHeader("Content-Encoding");  
  4.     InputStream instream = resp.getEntity().getContent();  
  5.     if (contentEncoding != null && contentEncoding.getValue().equalsIgnoreCase("gzip")) {  
  6.         instream = new GZIPInputStream(instream);  
  7.         BufferedReader br = new BufferedReader(new InputStreamReader(instream, cs));  
  8.         StringBuffer pageBodyBuf = new StringBuffer();  
  9.         String line;  
  10.         while((line=br.readLine())!=null) pageBodyBuf.append(String.format("%s\n", line));  
  11.         return pageBodyBuf.toString().trim();             
  12.     } else {                  
  13.         return EntityUtils.toString( resp.getEntity(), cs );              
  14.     }  
  15. }  
因此你便能使用下面的代碼取得 "http://pp.ceromulta.cl/" 的頁面內容: 
  1. HttpGet get = new HttpGet("http://pp.ceromulta.cl/");  
  2. HttpResponse resp = httpClient.execute(get);  
  3. String pageBody = HttpKit.GetPageBody(resp, "utf-8");  
  4. System.out.printf("\t[Info] Page Body:\n%s\n", pageBody);  
執行結果: 
[Info] Page Body:
301 Moved Permanently


<meta http-equiv="refresh" content="0; URL=Login.php?webapps/mpp/home">
 
而我們的重點在於上面的紅體字 meta http-equiv="refresh", 透過它可以設定頁面自動 redirect. 而這種類型的 redirect 是 HttpClient 無法處理的 redirect. 因此下面的範例代碼在於說明如何處理這類型的 redirect. 

範例代碼: 
第一步我們定義了一個 Pattern 來捕捉這樣語法的出現: 
  1. Pattern redirPtn = Pattern.compile("<(?:META|meta|Meta) (?:HTTP-EQUIV|http-equiv)=\"refresh\".*URL=(.*)\">");  

然後當我們發現有 meta refresh 的語法出現, 透過一個 while loop 一直處理 refresh URL 並幫我們導向 refresh URL 指定的位置, 直到該頁面沒有 meta refresh. 完整代碼如下: 
  1. Pattern redirPtn = Pattern.compile("<(?:META|meta|Meta) (?:HTTP-EQUIV|http-equiv)=\"refresh\".*URL=(.*)\">");  
  2. HttpContext localContext = new BasicHttpContext();  
  3. HttpGet get = new HttpGet("http://pp.ceromulta.cl/");         
  4. HttpResponse resp = httpClient.execute(get, localContext);  
  5. String pageBody = GetPageBody(resp, "utf-8");  
  6. Matcher mth = redirPtn.matcher(pageBody);  
  7. while(mth.find())  
  8. {  
  9.     String rurl = mth.group(1);  
  10.     if(!rurl.startsWith("http:")) {  
  11.         HttpUriRequest currentReq = (HttpUriRequest) localContext.getAttribute(ExecutionContext.HTTP_REQUEST);  
  12.         HttpHost currentHost = (HttpHost)  localContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST);  
  13.         String rdu = (currentReq.getURI().isAbsolute()) ? currentReq.getURI().toString() : (currentHost.toURI() + currentReq.getURI());                       
  14.         rurl = String.format("%s%s", rdu, rurl);                                                                                      
  15.     }                     
  16.     System.out.printf("\t[Info] Refresh URL=%s...\n", rurl);      
  17.     get = new HttpGet(rurl);  
  18.     pageBody = GetPageBody(httpClient.execute(get, localContext), "utf-8");  
  19.     mth = redirPtn.matcher(pageBody);  
  20. }  
  21. System.out.printf("\t[Info] Final Page Body:\n%s\n", pageBody);  
執行結果如下, 你可以發現總共經過 3 次 redirect (前兩次為 301,302), 而最後一個 redirect 是透過 meta refresh: 
[Test] Redirect to 'http://pp.ceromulta.cl/d3993414284beca7c9d20faab65c690f' #第一次 redirect
[Test] Redirect to 'http://pp.ceromulta.cl/d3993414284beca7c9d20faab65c690f/' #第二次 redirect 
[Info] Refresh URL=http://pp.ceromulta.cl/d3993414284beca7c9d20faab65c690f/Login.php?webapps/mpp/home... #第三次 redirect
[Info] Final Page Body:


...

Supplement: 
* Meta 十大功用

[Git 常見問題] error: The following untracked working tree files would be overwritten by merge

  Source From  Here 方案1: // x -----删除忽略文件已经对 git 来说不识别的文件 // d -----删除未被添加到 git 的路径中的文件 // f -----强制运行 #   git clean -d -fx 方案2: 今天在服务器上  gi...