標籤:android style blog http color 使用 ar java for
摘要:
1.本文將詳細介紹享元模式的原理和實際代碼中特別是Android系統代碼中的應用。
綱要:
1. 引入享元模式
2. 享元模式的概念及優缺點介紹
3. 享元模式在Android源碼中的應用
1.先來一個段子:
GG每天給MM至少發一條簡訊,而且每天入睡前是必有一條簡訊的,往往是一些瑣事和一些比較肉麻的情話。開始的一個月,GG還對此是樂不可支,隨著時間的推移,那些肉麻的話說了很多遍,自己也覺得厭煩了,而且更讓人不可忍耐的是這些肉麻的情話每次都要重複的輸入。GG把這一煩心事告訴了自己的好友K,K說,“你這個大傻瓜,怎麼不把一些你常用的話存放在你的手機中,這樣,要用的時候,直接拿來用就行了”,傻GG一聽,頓時覺得醍醐灌頂,於是立即在手機中存放入了“寶貝兒,晚安喔”、“你是我天使”,“寶貝兒,我永遠的愛你”等話語。(摘自Android大話設計模式)
2.享元模式介紹2.1什麼是享元模式?
為了節約記憶體資源,把具有重複性質的執行個體進行統一管理(結合Factory模式),使用時對象一般不建立新執行個體,只進行引用。Flyweight本是體育上的一個術語,用來表示輕量級。Flyweight,飛翔般的重量,說明使用它會使代碼變得“輕”(指記憶體消耗方面)起來。
2.2享元模式有什麼好處?
上面已經提到了,它最大也是唯一的好處就是:“輕”。享元能夠節省重複執行個體的記憶體消耗。舉兩個最經典的例子。
1、26個英文字母的點陣顯示資料。我們使用它們的時候,如果每次使用都建立新的字母執行個體,那記憶體浪費是很嚴重的。我們可以很容易的想到,將這26個點陣資料個建立一個執行個體,並專門用一個類管理起來。當使用者需要使用的時候,給他們這些執行個體的引用,這樣就能大大節省記憶體消耗了。
2、畫畫。假設我能畫的圖形是有限的(圓,長方形,棱形,梯形等),畫畫就是把這些圖形不斷的往上貼圖。當我一幅畫有上千個圖案的時候,享元模式就成為了很好的減少記憶體消耗的解決方案。我們把固定的圖形都只建立一個執行個體,等要畫的時候,使用它們的引用“作畫”即可。
不過這個畫畫的例子就有問題了:這些獨立圖形的位置,大小,顏色都不一樣,而執行個體只有一個,這樣畫出來豈不是都擠一塊去了?這裡是享元模式的關鍵的地方:區分內蘊態和外蘊態。簡單說就是:一部分(圖形)共用(稱為內部儲存或內部狀態),一部分(大小,位置,顏色)不公享(稱為外部儲存或外部狀態)。把這個概念加進去,享元模式就靈活多了。
3.源碼實戰:3.1String:
沒想到吧!String用到了享元模式。其實我也沒想到。就我個人理解,與其說String使用享元模式,不如說是用到了它的思想,畢竟它的實現不是通過活生生的Java代碼。下面一起來看一下:
String s0=”abc”;String s1=”abc”;String s2=”a” + “bc”;System.out.println( s0==s1 );System.out.println( s0==s2 );
結果為:
truetrue
我們都知道,String是一種對象(非基礎類型,下同),對象的==比較的是和指向的記憶體有關的。如果兩個對象指向的是同一個資料,那他們是相等的(Java語言是不支援重載的,所以沒有例外情況)。
但上面的寫法不應該是三個獨立的資料區段嗎?這裡就涉及到常量池。
常量池(constant pool)指的是在編譯期被確定,並被儲存在已編譯的.class檔案中的一些資料。它包括了關於類、方法、介面等中的常量,也包括字串常量。 這些字串對於編譯器來說,都是同樣的常量字串,所以編譯器把他們簡化成了一條資料,從而最佳化了記憶體使用量。這和享元模式的思想如出一轍。
如果我們這樣寫:
String s0=”abc”;String s1= new String(“abc”);System.out.println( s0==s1 );
結果為:
false
為什麼會這樣?因為s1等號右邊不是常量字串,而是執行個體化操作,人家編譯器不認這種非靜態東西。這也說明了一點,享元需要專門的類來管理,否則就會出現上面那種不適用的情況。一般來說都是用Factory。
3.2SQLiteCompiledSql:
(參考Android設計模式系列(6)--SDK源碼之享元模式 http://www.cnblogs.com/qianxudetianxia/archive/2011/08/10/2133659.html)
Android中SQLiteCompiledSql的使用,其實是很多資料庫系統典型的實現。從應用啟動,通過各種資料庫操作,我們不知道進行了多少次的查詢操作,而這些操作中又有相當一部分sql語句是相同的,這些編譯後的sql編譯對象其實是一樣的,是可以共用共用的,其實就是緩衝。SQLiteCompiledSql就是這樣的一個需要共用的享元對象。
UML圖:
其中SQLiteCompiledSql就是被管理享元對象,主要是內部狀態sql語句:
class SQLiteCompiledSql { private String mSqlStmt = null; native_compile(sql); native_finalize();}
享元對象只是存放固定內容的執行個體,具體實現精華在管理享元對象的工廠中。
SqliteDatabase就是管理享元對象的工廠,它裡面的mCompiledQuerie就是存放享元對象的容器。通常都是使用HashMap(HashTable的替代品,詳細http://oznyang.iteye.com/blog/30690)進行儲存。通過這種方式大大減少了sql編譯對象的建立,提高了資料庫操作的效能。
public class SQLiteDatabase{ Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap(); SQLiteCompiledSql getCompiledStatementForSql(String sql) { SQLiteCompiledSql compiledStatement = null; boolean cacheHit; synchronized(mCompiledQueries) { if (mMaxSqlCacheSize == 0) { return null; } cacheHit = (compiledStatement = mCompiledQueries.get(sql)) != null; } if (cacheHit) { mNumCacheHits++; } else { mNumCacheMisses++; } return compiledStatement; } private void deallocCachedSqlStatements() { synchronized (mCompiledQueries) { for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) { compiledSql.releaseSqlStatement(); } mCompiledQueries.clear(); } } void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) { //省略具體代碼 }}
大體享元工廠都是這樣一個結構:
HashMap:用作儲存(就和String的常量池一樣)
get方法:得到享元對象的引用
add方法:增加新的享元對象,用作初始化,一般使用者不會接觸到。也有更靈活的享元模式使用會動態增加HashMap沒有的享元對象(設計模式是死的,人寫出來的代碼是活的,怎麼用全憑大家)。
dealloc方法:釋放所有的享元對象,Java有自己的記憶體回收機制,這個可選。
著作權,轉載請註明出處:
http://www.cnblogs.com/sickworm/p/4016026.html
【設計模式】享元模式(Flyweight)