lru演算法java
Ⅰ 簡要說明oracle資料庫體系的內存結構
內存結構 oracle內存結構大致具有四個區:軟體代碼區、系統全局區、程序全局區和排序區。 1、系統全局區。(SGA) 系統全局區為一組由oracle分配的共享數據結構,它是實例的主要部分,它含有數據維護、SQL語句分析與重做緩存所必須的所有內存結構,系統全局區的數據是共享的,也就是說,多個進程可以在同一時間對SGA中的數據進行訪問和修改。它包含以下內容: <1>、數據塊緩沖區 該區存放最近使用過的數據塊,使用LRU(最近最少使用演算法)進行管理。 <2>、字典緩沖區 該區用於保存數據字典中的行,數據字典中存放oracle系統管理自身所需的所有信息。該區也使用LRU演算法管理。 <3>、重做日誌緩沖區 任何事務在記錄到重做日誌之前都先放到該區,資料庫系統定期將該區內容寫入到聯機重做日誌中。 <4>、SQL共享池 存放所有通過SQL語法分析、准備執行的SQL語句。 <5>、java池 為JAVA命令提供語法分析。 <6>、多緩沖池 可以在SGA中創建多個緩沖池,能夠用多個緩沖池把的數據集與其他的應用程序分開,以減少它們爭奪數據塊緩沖區相同資源的可 能性。 2、程序全局區(PGA) 包含單個伺服器進程或單個後台進程的數據和控制信息,與幾個進程共享的SGA 正相反PGA 是只被一個進程使用的區域,PGA 在創建進程時分配在終止進程時回收。 3、排序區 排序需要內存,這部分空間成為排序區,排序區存在於請求排序的用戶進程的內存中,該空間的大小為適應排序數據量的大小,可增長,但受初始化參數SORT_AREA_SIZER所限制。 4、軟體代碼區 用於存儲正在執行或可以執行的程序代碼。 </FONT></SPAN>
Ⅱ 創建一個序列,第一次從5循環到10,以後再從0開始循環。如果數據的控制文件損壞了,需要如何解決故障。
1、 ORACLE 實例――包括內存結構與後台進程 2、 ORACLE 資料庫――物理操作系統文件的集合 3、 了解內存結構的組成 4、 了解後台進程的作用
1、 Oracle 實例――包括內存結構與後台進程
2、 Oracle 資料庫――物理操作系統文件的集合
3、 了解內存結構的組成
4、 了解後台進程的作用
5、 了解資料庫的物理文件
6、 解釋各種邏輯結構
一、Oracle實例
1、Oracle 實例
System Global Area(SGA) 和 Background Process 稱為資料庫的實例。
2、Oracle 資料庫
一系列物理文件的集合(數據文件,控制文件,聯機日誌,參數文件等)
3、系統全局共享區System Global Area(SGA)
System Global Area 是一塊巨大的共享內存區域,他被看做是Oracle 資料庫的一個大緩沖池,這里的數據可以被Oracle的各個進程共用。其大小可以通過如下語句查看:
SQL> select * from v$sga;
NAME VALUE
-------------------- ---------
Fixed Size 39816
Variable Size 259812784
Database Buffers 1.049E+09
Redo Buffers 327680
更詳細的信息可以參考V$sgastat、V$buffer_pool
主要包括以下幾個部分:
a、 共享池(Shared pool)
共享池是SGA中最關鍵的內存片段,特別是在性能和可伸縮性上。一個太小的共享池會扼殺性能,使系統停止,太大的共享池也會有同樣的效果,將會消耗大量的CPU來管理這個共享池。不正確的使用共享池只會帶來災難。共享池主要又可以分為以下兩個部分:
SQL語句緩沖(Library Cache)
當一個用戶提交一個SQL語句,Oracle會將這句SQL進行分析(parse),這個過程類似於編譯,會耗費相對較多的時間。在分析完這個SQL,Oracle會把他的分析結果給保存在Shared pool的Library Cache中,當資料庫第二次執行該SQL時,Oracle自動跳過這個分析過程,從而減少了系統運行的時間。這也是為什麼第一次運行的SQL 比第二次運行的SQL要慢一點的原因。
下面舉例說明parse的時間
SQL> select count(*) fromscpass ;
COUNT(*)
----------
243
Elapsed: 00:00:00.08
這是在Share_pool 和Data buffer 都沒有數據緩沖區的情況下所用的時間
SQL> alter system flush SHARED_POOL;
System altered.
清空Share_pool,保留Data buffer
SQL> select count(*) from scpass ;
COUNT(*)
----------
243
Elapsed: 00:00:00.02
SQL> select count(*) from scpass ;
COUNT(*)
----------
243
Elapsed: 00:00:00.00
從兩句SQL 的時間差上可以看出該SQL 的Parse 時間約為00:00:00.02
對於保存在共享池中的SQL語句,可以從V$Sqltext、v$Sqlarea中查詢到,對於編程者來說,要盡量提高語句的重用率,減少語句的分析時間。一個設計的差的應用程序可以毀掉整個資料庫的Share pool,提高SQL語句的重用率必須先養成良好的變成習慣,盡量使用Bind變數。
數據字典緩沖區(Data Dictionary Cache)
顯而易見,數據字典緩沖區是Oracle特地為數據字典准備的一塊緩沖池,供Oracle內部使用,沒有什麼可以說的。
b、塊緩沖區高速緩存(Database Buffer Cache)
這些緩沖是對應所有數據文件中的一些被使用到的數據塊。讓他們能夠在內存中進行操作。在這個級別里沒有系統文件,,戶數據文件,臨時數據文件,回滾段文件之分。也就是任何文件的數據塊都有可能被緩沖。資料庫的任何修改都在該緩沖里完成,並由DBWR進程將修改後的數據寫入磁碟。
這個緩沖區的塊基本上在兩個不同的列表中管理。一個是塊的「臟」表(Dirty List),需要用資料庫塊的
書寫器(DBWR)來寫入,另外一個是不臟的塊的列表(Free List),一般的情況下,是使用最近最少使用 (Least Recently Used,LRU)演算法來管理。塊緩沖區高速緩存又可以細分為以下三個部分(Default pool,Keep pool,Recycle pool)。如果不是人為設置初始化參數(Init.ora),Oracle將默認為Default pool。由於操作系統定址能力的限制,不通過特殊設置,在32位的系統上,塊緩沖區高速緩存最大可以達到1.7G,在64位系統上,塊緩沖區高速緩存最大可以達到10G。
c、重做日誌緩沖區(Redo log buffer)
重做日誌文件的緩沖區,對資料庫的任何修改都按順序被記錄在該緩沖,然後由LGWR進程將它寫入磁碟。這些修改信息可能是DML語句,如(Insert,Update,Delete),或DDL語句,如(Create,Alter,Drop等)。 重做日誌緩沖區的存在是因為內存到內存的操作比較內存到硬碟的速度快很多,所以重作日誌緩沖區可以加快資料庫的操作速度,但是考慮的資料庫的一致性與可恢復性,數據在重做日誌緩沖區中的滯留時間不會很長。所以重作日誌緩沖區一般都很小,大於3M之後的重作日誌緩沖區已經沒有太大的實際意義。
d、Java程序緩沖區(Java Pool)
Java 的程序區,Oracle 8I 以後,Oracle 在內核中加入了對Java的支持。該程序緩沖區就是為Java 程序保留的。如果不用Java程序沒有必要改變該緩沖區的默認大小。
e、大池(Large Pool)
大池的得名不是因為大,而是因為它用來分配大塊的內存,處理比共享池更大的內存,在8.0開始引入。
下面對象使用大池:
MTS――在SGA的Large Pool中分配UGA
語句的並行查詢(Parallel Executeion of Statements)――允許進程間消息緩沖區的分配,用來協調 並行查詢伺服器
備份(Backup)――用於RMAN磁碟I/O緩存
4、後台進程(Background process)
後台進程是Oracle的程序,用來管理資料庫的讀寫,恢復和監視等工作。Server Process主要是通過他和user process進行聯系和溝通,並由他和user process進行數據的交換。在Unix機器上,Oracle後台進程相對於操作系統進程,也就是說,一個Oracle後台進程將啟動一個操作系統進程;在Windows機器上, Oracle後台進程相對於操作系統線程,打開任務管理器,我們只能看到一個Oracle.EXE的進程,但是通過另外的工具,就可以看到包含在這里進程中的線程。
在Unix上可以通過如下方法查看後台進程:
ps ?ef | grep ora_
# ps -ef | grep ora_ | grep XCLUAT
Oracle 29431 1 0 Sep 02 2:02 ora_dbwr_SID
Oracle 29444 1 0 Sep 02 0:03 ora_ckpt_SID
Oracle 29448 1 0 Sep 02 2:42 ora_smon_SID
Oracle 29442 1 0 Sep 02 3:25 ora_lgwr_SID
Oracle 29427 1 0 Sep 02 0:01 ora_pmon_SID
a、Oracle系統有5 個基本進程他們是
DBWR(數據文件寫入進程)
LGWR(日誌文件寫入進程)
SMON(系統監護進程)
PMON(用戶進程監護進程)
CKPT(檢查點進程,同步數據文件, 日誌文件,控制文件)
b、DBWR
將修改過的數據緩沖區的數據寫入對應數據文件
維護系統內的空緩沖區
這里指出幾個容易錯誤的概念:
當一個更新提交後,DBWR把數據寫到磁碟並返回給用戶提交完成.
DBWR會觸發CKPT 後台進程
DBWR不會觸發LGWR 進程
上面的概念都是錯誤的.
DBWR是一個很底層的工作進程,他批量的把緩沖區的數據寫入磁碟。和任何前台用戶的進程幾乎沒有什麼關系,也不受他們的控制。至於DBWR會不會觸發LGWR和CKPT進程,我們將在下面幾節里討論。
DBWR工作的主要條件如下
DBWR 超時
系統中沒有多的空緩沖區用來存放數據
CKPT 進程觸發DBWR 等
c、LGWR
將重做日誌緩沖區的數據寫入重做日誌文件,LGWR是一個必須和前台用戶進程通信的進程。當數據被修改的時候,系統會產生一個重做日誌並記錄在重做日誌緩沖區內。這個重做日誌可以類似的認為是以下的一個結構:
SCN=000000001000
數據塊ID
對象ID=0801
數據行=02
修改後的數據=0011
提交的時候,LGWR必須將被修改的數據的重做日誌緩沖區內數據寫入日誌數據文件,然後再通知前台進程提交成功,並由前台進程通知用戶。從這點可以看出LGWR承擔了維護系統數據完整性的任務。
LGWR 工作的主要條件如下
用戶提交
有1/3 重做日誌緩沖區未被寫入磁碟
有大於1M 重做日誌緩沖區未被寫入磁碟
超時
DBWR需要寫入的數據的SCN號大於LGWR 記錄的SCN號,DBWR 觸發LGWR寫入
d、SMON
工作主要包含
清除臨時空間
在系統啟動時,完成系統實例恢復
聚結空閑空間
從不可用的文件中恢復事務的活動
OPS中失敗節點的實例恢復
清除OBJ$表
縮減回滾段
使回滾段離線
e、PMON
主要用於清除失效的用戶進程,釋放用戶進程所用的資源。如PMON將回滾未提交的工作,釋放鎖,釋放分配給失敗進程的SGA資源。
f、CKPT
同步數據文件,日誌文件和控制文件,由於DBWR/LGWR的工作原理,造成了數據文件,日誌文件,控制文件的不一至,這就需要CKPT進程來同步。CKPT會更新數據文件/控制文件的頭信息。
CKPT工作的主要條件如下
在日誌切換的時候
資料庫用immediate ,transaction , normal 選項shutdown 資料庫的時候
根據初始話文件LOG_CHECKPOINT_INTERVAL、LOG_CHECKPOINT_TIMEOUT、FAST_START_IO_TARGET 的設置的數值來確定
用戶觸發
以下進程的啟動需要手工配置
g、ARCH
當資料庫以歸檔方式運行的時候,Oracle會啟動ARCH進程,當重做日誌文件被寫滿時,日誌文件進行切換,舊的重做日誌文件就被ARCH進程復制到一個/多個特定的目錄/遠程機器。這些被復制的重做日誌文件被叫做歸檔日誌文件。
h、RECO
負責解決分布事物中的故障。Oracle可以連接遠程的多個資料庫,當由於網路問題,有些事物處於懸而未決的狀態。RECO進程試圖建立與遠程伺服器的通信,當故障消除後,RECO進程自動解決所有懸而未決的會話。
i、服務進程Server Process
服務進程的分類
專用服務進程(Dedicated Server Process)
一個服務進程對應一個用戶進程
共享服務進程(MultiTreaded Server Process)
一個服務進程對應多個用戶進程,輪流為用戶進程服務。
PGA & UGA
PGA = Process Global Area
UGA = User Global Area
他保存了用戶的變數、許可權、堆棧、排序空間等用戶信息,對於專用伺服器進程,UGA在PGA中分配。對於多線程進程,UGA在Large pool中分配。
j、用戶進程User Process
在客戶端,將用戶的SQL 語句傳遞給服務進程
5、一個貫穿資料庫全局的概念----系統改變號SCN(System Change Number)
系統改變號,一個由系統內部維護的序列號。當系統需要更新的時候自動增加,他是系統中維持數據的一致性和順序恢復的重要標志。
a. 查詢語句不會使SCN增加,就算是同時發生的更新,資料庫內部對應的SCN也是不同的。這樣一來就保證了數據恢復時候的順序。
b. 維持數據的一致性,當一
二、Oracle 資料庫
Oracle資料庫的組成――物理操作系統文件的集合。主要包括以下幾種。
1、控制文件(參數文件init.ora記錄了控制文件的位置)
控制文件包括如下主要信息
資料庫的名字,檢查點信息,資料庫創建的時間戳
所有的數據文件,聯機日誌文件,歸檔日誌文件信息
備份信息等
有了這些信息,Oracle就知道那些文件是數據文件,現在的重做日誌文件是哪些,這些都是系統啟動和運行的基本條件,所以他是Oracle運行的根本。如果沒有控制文件系統是不可能啟動的。控制文件是非常重要的,一般採用多個鏡相復制來保護控制文件,或採用RAID來保護控制文件。控制文件的丟失,將使資料庫的恢復變的很復雜。
控制文件信息可以從V$Controlfile中查詢獲得
2、數據文件(數據文件的詳細信息記載在控制文件中)
可以通過如下方式查看數據文件
SQL> select name from v$datafile;
NAME
---------------------------------------------
/u05/dbf/PROD/system_01.dbf
/u06/dbf/PROD/temp_01.dbf
/u04/dbf/PROD/users_01.dbf
/u09/dbf/PROD/rbs_01.dbf
/u06/dbf/PROD/applsys_indx_01.dbf
/u05/dbf/PROD/applsys_data_01.dbf
從以上可以看出,數據文件大致可以分為以下幾類:
i. 系統數據文件(system_01.dbf)
存放系統表和數據字典,一般不放用戶的數據,但是用戶腳本,如過程,函數,包等卻是保存在數據字典中的。
名詞解釋:數據字典 數據字典是一些系統表或視圖,他存放系統的信息,他包括資料庫版本,數據文件信息,表與索引等段信息,系統的運行狀態等各種和系統有關的信息和用戶腳本信息。資料庫管理員可以通過對數據字典的查詢,就可以了解到Oracle的運行狀態。
ii. 回滾段文件(rbs_01.dbf)
如果資料庫進行對數據的修改,那麼就必須使用回滾段,回滾段是用來臨時存放修改前的數據(Before Image)。回滾段通常都放在一個單獨的表空間上(回滾表空間),避免表空間碎片化,這個表空間包含的數據文件就是回滾數據文件。
iii. 臨時數據文件(temp_01.dbf)
主要存放用戶的排序等臨時數據,與回滾段相似,臨時段也容易引起表空間碎片化,而且沒有辦法在一個永久表空間上開辟臨時段,所以就必須有一個臨時表空間,它所包含的數據文件就是臨時數據文件,主要用於不能在內存上進行的排序操作。我們必須為用戶指定一個臨時表空間。
iv. 用戶數據文件(/applsys_data_01.dbf ,applsys_indx_01.dbf)
存放用戶數據,這里列舉了兩類常見的用戶型數據,一般數據和索引數據,一般來說,如果條件許可的話,可以考慮放在不同的磁碟上。
3、重做日誌文件(聯機重做日誌)
用戶對資料庫進行的任何操作都會記錄在重做日誌文件。在了解重做日誌之前必須了解重做日誌的兩個概念,重做日誌組和重做日誌組成員(Member),一個資料庫中至少要有兩個日誌組文件,一組寫完後再寫另一組,即輪流寫。每個日誌組中至少有一個日誌成員,一個日誌組中的多個日誌成員是鏡相關系,有利於日誌文件的保護,因為日誌文件的損壞,特別是當前聯機日誌的損壞,對資料庫的影響是巨大的。
聯機日誌組的交換過程叫做切換,需要特別注意的是,日誌切換在一個優化效果不好的資料庫中會引起臨時的「掛起」。掛起大致有兩種情況:
在歸檔情況下,需要歸檔的日誌來不及歸檔,而聯機日誌又需要被重新利用
檢查點事件還沒有完成(日誌切換引起檢查點),而聯機日誌需要被重新利用
解決這種問題的常用手段是:
i.增加日誌組
ii.增大日誌文件成員大小
通過v$log可以查看日誌組,v$logfile可以查看具體的成員文件。
4、歸檔日誌文件
Oracle可以運行在兩種模式之中,歸檔模式和不歸檔模式。如果不用歸檔模式,當然,你就不會有歸檔日誌,但是,你的系統將不會是一個實用系統,特別是不能用於生產系統,因為你可能會丟失數據。但是在歸檔模式中,為了保存用戶的所有修改,在重做日誌文件切換後和被覆蓋之間系統將他們另外保存成一組連續的文件系列,該文件系列就是歸檔日誌文件。
有人或許會說,歸檔日誌文件佔領我大量的硬碟空間,其實,具體想一想,你是願意浪費一點磁碟空間來保護你的數據,還是願意丟失你的數據呢?顯而義見,我們需要保證我們的數據的安全性。其實,歸檔並不是一直佔領你的磁碟空間,你可以把她備份到磁帶上,或則刪除上一次完整備份前的所有日誌文件。
5、初始化參數文件
initSID.ora或init.ora文件,因為版本的不一樣,其位置也可能會不一樣。在8i中,通常位於$Oracle_HOME/admin//Pfile下,初始化文件記載了許多資料庫的啟動參數,如內存,控制文件,進程數等,在資料庫啟動的時候載入(Nomount時載入),初始化文件記錄了很多重要參數,對資料庫的性能影響很大,如果不是很了解,不要輕易亂改寫,否則會引起資料庫性能下降。
6、其他文件
i . 密碼文件
用於Oracle 的具有sysdba許可權用戶的認證.
ii. 日誌文件
報警日誌文件(alert.log或alrt.ora)
記錄資料庫啟動,關閉和一些重要的出錯信息。資料庫管理員應該經常檢查這個文件,並對出現的問題作出即使的反應。你可以通過以下SQL 找到他的路徑select value from v$PARAMETER where name ="background_mp_dest";
後台或用戶跟蹤文件
系統進程或用戶進程出錯前寫入的信息,一般不可能讀懂,可以通過Oracle的TKPROF工具轉化為可以讀懂的格式。對於系統進程產生的跟蹤文件與報警日誌文件的路徑一樣,用戶跟蹤文件的路徑,你可以通過以下SQL找到他的路徑select value from v$PARAMETER where name ="user_mp_dest";
三、Oracle邏輯結構
1、 表空間(tablespace)
表空間是資料庫中的基本邏輯結構,一系列數據文件的集合。一個表空間可以包含多個數據文件,但是一個數據文件只能屬於一個表空間。
2、 段(Segment)
段是對象在資料庫中佔用的空間,雖然段和資料庫對象是一一對應的,但段是從資料庫存儲的角度來看的。一個段只能屬於一個表空間,當然一個表空間可以有多個段。
表空間和數據文件是物理存儲上的一對多的關系,表空間和段是邏輯存儲上的一對多的關系,段不直接和數據文件發生關系。一個段可以屬於多個數據文件,關於段可以指定擴展到哪個數據文件上面。
段基本可以分為以下四種
數據段(Data Segment)
索引段(Index Segment)
回滾段(Rollback Segment)
臨時段(Temporary Segment)
3、區間(Extent)
關於Extent的翻譯有多種解釋,有的譯作擴展,有的譯作盤區,我這里通常譯為區間。在一個段中可以存在多個區間,區間是為數據一次性預留的一個較大的存儲空間,直到那個區間被用滿,資料庫會繼續申請一個新的預留存儲空間,即新的區間,一直到段的最大區間數(Max Extent)或沒有可用的磁碟空間可以申請。 在Oracle8i以上版本,理論上一個段可以無窮個區間,但是多個區間對Oracle卻是有性能影響的,Oracle建議把數據分布在盡量少的區間上,以減少Oracle的管理與磁頭的移動。
4、Oracle數據塊(Block)
Oracle最基本的存儲單位,他是OS數據塊的整數倍。Oracle的操作都是以塊為基本單位,一個區間可以包含多個塊(如果區間大小不是塊大小的整數倍,Oracle實際也擴展到塊的整數倍)。
5、基本表空間介紹
a. 系統表空間
主要存放數據字典和內部系統表基表
查看數據數據字典的SQL
select * from dict
查看內部系統表的SQL
select * from v$fixed_view_definition
DBA對系統的系統表中的數據字典必須有一個很深刻的了解,他們必須准備一些基礎的SQL語句,通過這些SQL可以立即了解系統的狀況和資料庫的狀態,這些基本的SQL包括
系統的剩餘空間
系統的SGA
狀態系統的等待
用戶的許可權
當前的用戶鎖
緩沖區的使用狀況等
在成為DBA 的道路上我們不建議你過分的依賴於OEM/Quest 等優秀的資料庫管理工具,因為他們不利於你對數據數據字典的理解,SQL語句可以完成幾乎全部的資料庫管理工作。
大量的讀少量的寫是該表空間的一個顯著的特點。
b. 臨時表空間.
臨時表空間顧名思義是用來存放臨時數據的,例如排序操作的臨時空間,他的空間會在下次系統啟動的時候全部被釋放。
c. 回滾段表空間
i. 回滾段在系統中的作用
當資料庫進行更新插入刪除等操作的時候,新的數據被更新到原來的數據文件,而舊的數據(Before Image)就被放到回滾段中,如果數據需要回滾,那麼可以從回滾段將數據再復制到數據文件中。來完成數據的回滾。在系統恢復的時候, 回滾段可以用來回滾沒有被commit 的數據,解決系統的一至性。
回滾段在什麼情況下都是大量的寫,一般是少量讀,因此建議把回滾段單獨出來放在一個單獨的設備(如單獨的磁碟或RAID),以減少磁碟的IO爭用。
ii. 回滾段的工作方式
一個回滾表空間可以被劃分成多個回滾段.
一個回滾段可以保存多個會話的數據.
回滾段是一個圓形的數據模型
假設回滾段由4 個區間組成,他們的使用順序就是區間1à區間2à區間3à區間4à區間1。也就是說,區間是可以循環使用的,當區間4到區間1的時候,區間1裡面的會話還沒有結束, 區間4用完後就不能再用區間1,這時系統必須分配區間5,來繼續為其他會話服務服務。
我們分析一個Update 語句的完成
①. 用戶提交一個Update 語句
②. Server Process 檢查內存緩沖.
如果沒有該數據塊的緩沖,則從磁碟讀入
i. 如果沒有內存的有效空間,DBWR被啟動將未寫入磁碟的臟緩沖寫入磁碟
ii. 如果有有效空間,則讀入
③. 在緩沖內更新數據
i. 申請一個回滾段入口,將舊數據寫如回滾段
ii. 加鎖並更新數據
iii. 並在同時將修改記錄在Redo log buffer中
另外,站長團上有產品團購,便宜有保證
Ⅲ java web開發緩存方案,ehcache和redis哪個更好
Ehcache
在java項目廣泛的陸塌使用。它是一個開源的、設計於提高在數據從RDBMS中取出來的高花費、高延遲採取的一種緩存方案。正因為Ehcache具有健壯性(基於java開發)、被認證(具有apache 2.0 license)、充滿特色(稍後會詳細介紹),所以被用於大型復雜分布式web application的各個節點中。
1. 夠快
Ehcache的發行有一段時長了,經過幾年的努力和不計其數的性能測試盯豎,Ehcache終被設計於large, high concurrency systems.
2. 夠簡單
開發者提供的介面非常簡單明了,從Ehcache的搭建到運用運行僅僅需要的是你寶貴的幾分鍾。其實很多開發者都不知道自己用在用Ehcache,Ehcache被廣泛的運用於早則圓其他的開源項目
比如:hibernate
3.夠袖珍
關於這點的特性,官方給了一個很可愛的名字small foot print ,一般Ehcache的發布版本不會到2M,V 2.2.3 才 668KB。
4. 夠輕量
核心程序僅僅依賴slf4j這一個包,沒有之一!
5.好擴展
Ehcache提供了對大數據的內存和硬碟的存儲,最近版本允許多實例、保存對象高靈活性、提供LRU、LFU、FIFO淘汰演算法,基礎屬性支持熱配置、支持的插件多
6.監聽器
緩存管理器監聽器 (CacheManagerListener)和 緩存監聽器(CacheEvenListener),做一些統計或數據一致性廣播挺好用的
Ⅳ LRU綆楁硶鐨勫師鐞嗕笌瀹炵幇
LRU鏄疞east Recently Used鐨勭緝鍐欙紝鍗蟲渶榪戞渶灝戜嬌鐢ㄧ畻娉曪紝搴旂敤闈㈤潪甯哥殑騫挎硾錛屾瘮濡俽edis褰撲腑鐨勫唴瀛樻窐奼扮瓥鐣ャ傚洜涓鴻$畻鏈虹殑鍐呭瓨閮芥槸鏈夐檺鐨勶紝褰撶敤鎴瘋塊棶鐨勬暟鎹濡傛灉瀛樺湪鍐呭瓨褰撲腑鐩存帴榪斿洖鐨勭粰鐢ㄦ埛鐨勮瘽錛屾晥鐜囦細闈炲父蹇錛屼絾鏄濡傛灉鍐呭瓨涓嶅瓨鍦錛屽湪鍘葷佺洏閲岄潰鏌ユ壘鐨勶紝鏁堢巼浼氬ぇ鎵撴姌鎵c傛墍浠ユ垜浠甯屾湜鍦ㄦ湁闄愮殑鍐呭瓨絀洪棿褰撲腑錛屽氬瓨鏀劇偣鐑鐐規暟鎹錛岀敤鎴蜂笉緇忓父璁塊棶鐨勬暟鎹錛屽敖閲忔窐奼版帀錛岄伩鍏嶅崰鐢ㄥ唴瀛樼┖闂淬
浣跨敤鍙屽悜閾捐〃鏉ュ疄鐜癓RU 榪欑瘒鏂囩珷宸茬粡鐢ㄥ弻鍚戦摼琛ㄦ潵瀹炵幇榪嘗RU綆楁硶浜嗭紝浣嗘槸鍩轟簬鍙屽悜閾捐〃鐨勭壒鎬э紝浣垮緱璇ョ畻娉曠殑鏃墮棿澶嶆潅搴︿負O(n)錛屾樉鐒朵笉鏄鏈浼樼殑綆楁硶錛岄偅涔堟湁娌℃湁綆楁硶錛屽彲浠ヨ揪鍒癘(1)錛屽綋鐒舵槸鏈夌殑錛屾棭鏃╃殑璁$畻鏈虹戝﹀跺凡緇忔兂鍒幫紝騫朵笖宸茬粡瀹炵幇浜嗐
鍦ㄧ瑪鑰呬粙緇嶆帴涓嬫潵鐨勫唴瀹規椂錛岃繕鏄甯屾湜鍏堜簡瑙d竴涓嬩袱綃囧崥鏂囷細
涓銆 鍥捐ВHashMap鍘熺悊
浜屻 鍥捐ВLinkedHashMap
涔嬪墠浣跨敤鍙屽悜閾捐〃鍘誨疄鐜癓RU綆楁硶鏃訛紝鏃墮棿澶嶆潅搴︽病鏈夎揪鍒癘(1)錛屼富瑕佸師鍥犲湪浜庨亶鍘嗙粨鐐規椂錛屽甫鏉ョ殑鏃墮棿寮閿錛岄偅涔堟崲鍙ヨ瘽璇達紝瑕佸疄鐜伴亶鍘嗙粨鐐規椂錛屾椂闂村嶆潅搴︿負O(1)錛岀涓鏃墮棿鎯沖埌鐨勫簲璇ユ槸hash鏁扮粍錛屼絾鏄痟ash綆楁硶鍙鑳戒細瀛樺湪涓嶅悓鐨刱ey鍊礆紝浜х敓鐩稿悓鐨刪ash鍊礆紝閭d箞鍙浠ュ皢涓嶅悓key錛屼絾鏄鐩稿悓hash鍊肩殑緇撶偣錛屼互鍗曢摼琛ㄧ殑褰㈠紡瀛樻斁銆傝繖鏍蜂粎浠呮槸瀹炵幇浜嗗瓨鍙栨椂闂村嶆潅搴︿負O(1)錛屽備綍瀹炵幇鏁版嵁鑳藉熸寜璁塊棶欏哄簭瀛樻斁鍛錛熷苟涓斿炲垹鐨勬椂闂村嶆潅搴︿負O(1)錛岃繖涓鍙浠ヤ嬌鐢ㄥ弻鍚戦摼琛ㄦ潵瀹炵幇錛屾墍浠ョ患鍚堟潵璁詫紝灝辨槸瀹炵幇鏁e垪鏁扮粍+鍙屽悜閾捐〃鏉ヤ嬌鐢↙RU錛屽彲浠ヨ揪鍒版椂闂村嶆潅搴︿負O(1)銆
閫昏緫瑙嗗浘濡備笅:
鍜嬩竴鐪嬭繖涓鍥句貢鐨勫緢錛岀◢寰瑙i噴涓涓嬶紝濡傛灉鎰熻夌悊瑙d笂鏈夌偣鍥伴毦錛屽彲浠ュ厛鍘諱簡瑙d竴涓嬩箣鍓嶆帹鑽愮殑涓ょ瘒鍗氭枃錛岄偅閲屼細浠嬬粛鐨勬洿鍔犺︾粏涓鐐廣
1.鏈宸︿晶鍏跺疄灝辨槸涓涓鏅閫氱殑鏁扮粍錛屾暟緇勭殑澶у皬蹇呴』鏄2鐨勫嶆暟錛岃繖涓鍘熷洜鏄浠涔堝憿錛熷洜涓鴻繖涓鏁扮粍鐨勫瓨鏀炬柟寮忔槸鏁e垪鐨勶紝鎰忔濆氨鏄闇瑕乲ey.hashcode & (length -1)鎵嶈兘寰楀嚭瀛樻斁浣嶇疆鐨勬柟寮忥紝hash鐨勫ソ澶勬槸鍙浠ユ牴鎹甼ey鍊礆紝鍦ㄦ椂闂村嶆潅搴︿負O(1)鐨勫墠鎻愭壘鍒板瑰簲鐨勫瓨鏀句綅緗錛岃繖涔熸槸鎴戜滑鐨勫垵琛鳳紝璇村埌榪欓噷鍏跺疄榪樻病鏈夎В閲婁負浠涔堜竴瀹氳佹槸2鐨勫嶆暟錛屽洜涓2鐨勫嶆暟-1錛岃繖涓鏁扮殑浜岃繘鍒訛紝涓瀹氬叏鏄1錛屾瘮濡16-1=15錛屼簩榪涘埗琛ㄧず灝辨槸1111錛&榪愮畻絎﹀叾瀹炲氨鏄灝嗗煎叏閮ㄥ寲鎴愪簩榪涘埗閫愪綅涓庯紝姣斿10111011 & 1111 = 1011 = 11錛屼絾鏄濡傛灉涓嶆槸2鐨勫嶆暟錛屾瘮濡7-1=6錛屽寲鎴愪簩榪涘埗灝辨槸0110錛岀敱浜庢湯浣嶆槸0錛屼笉綆′粈涔堜簩榪涘埗鍊間笌0110鍋&榪愮畻錛屼竴瀹氭槸鍋舵暟錛岃繖鏍蜂細瀵艱嚧鏁e垪鍒嗗竷涓嶅潎鍖銆
2.涓嶅悓key鍊礆紝鐩稿悓hash鍊礆紝濡備綍瀛樻斁鍛錛熺浉鍚岀殑hash鍊煎仛涓庤繍綆椾竴瀹氳兘澶熷緱鍒扮浉鍚岀殑鏁扮粍鑴氭爣錛岃繖浜涚粨鐐癸紝浠ュ崟閾捐〃鐨勫艦寮忓瓨鍦錛屽氨鏄鍥句腑鏁扮粍鍙充晶鐨勫崟閾捐〃銆
3.濡備綍瀹炵幇鎸夎塊棶欏哄簭錛熶笂鍥鵑櫎鍘繪暟緇勫拰鎸傚湪鏁扮粍鍙充晶鐨勫崟閾捐〃錛岃繕鏈夌豢鑹插拰榛勮壊鐨勫崟鍚戠澶達紝鍦ㄥ彸涓婅掕繕鏈変竴涓鍙屽悜閾捐〃鐨勫ご鎸囬拡銆傚叾瀹炶繖浜涚澶村氨鏄緇存姢涓嶅悓緇撶偣鐨勮塊棶欏哄簭錛屽嵆鍙屽悜閾捐〃銆
鎬諱笂鎵榪幫紝榪欑嶆暟鎹緇撴瀯瀹氫箟鐨勭粨鏋勪綋濡備笅錛
class Node{
Object key; //瀛樻斁key鍊礆紝鐢ㄤ簬璁$畻瀛樻斁鏁扮粍鑴氭爣浣嶇疆
Object value;//瀛樻斁鍏冪礌鍊
int hash;//瀛樻斁key.hashcode
Node next;//緇存姢鍗曢摼琛ㄩ『搴
Node before;//緇存姢鍙屽悜閾捐〃欏哄簭
Node after;
}
絎旇呯敤java瀹炵幇濡備笅錛屾劅鍏磋叮鍙浠ョ敤鑷宸卞枩嬈㈢殑璇璦鍘誨疄鐜頒竴閬嶏紝鍔犳繁鐞嗚В錛
鍏跺疄浠ヤ笂瀹炵幇搴曞眰鍘熺悊灝辨槸LinkedHashMap鐨勫疄鐜板師鐞嗭紝鍙涓嶈繃絎旇呭仛浜嗕竴浜涚畝鍖栵紝鍘繪帀浜嗙箒鐞愮殑緇ф壙錛屾墿瀹圭瓑錛岀獊鍑轟簡綆楁硶鏍稿績錛屽傛灉璇昏呮劅鍏磋叮涔熷彲浠ュ幓鐮旂┒涓涓婰inkedHashMap鐨勬簮鐮併
浣跨敤LinkedHashMap鏉ュ疄鐜癓RU綆楁硶錛
鐪嬭搗鏉ユ槸涓嶆槸綆鍗曚簡寰堝氾紝鍥犱負LinkedHashMap搴曞眰宸茬粡灝佽呭ソ浜嗭紝鎴戜滑鐩存帴璋冪敤灝卞ソ錛屼絾鏄浣滀負涓涓鎯寵佸彉浼樼鐨勭爜鍐滐紝涓瀹氳佺煡鍏剁劧鐭ュ叾鎵浠ョ劧銆
浣跨敤鏁e垪+鍙屽悜閾捐〃鐨勬柟寮忔槸濡備綍瀹炵幇O(1)澶嶆潅搴︾殑錛熷湪瀹炵幇LRU綆楁硶榪囩▼涓錛屾棤闈炰袱縐嶆搷浣滐紝鏌ユ壘鍜屼慨鏀癸紝浣跨敤鏁e垪鏁扮粍瀹炵幇鏌ユ壘鏃墮棿澶嶆潅搴︿負O(1)錛屼嬌鐢ㄥ弻鍚戦摼琛ㄥ疄鐜頒慨鏀瑰嶆潅搴︿負O(1)錛屽苟涓斿弻鍚戦摼琛ㄨ繕鍙浠ョ淮鎶よ塊棶欏哄簭錛屾墍浠ヤ嬌鐢ㄨ繖縐嶆柟寮忥紝鍙浠ヨ揪鍒癘(1)銆
Ⅳ 如何高效使用和管理Bitmap
一、圖片載入流程
首先,我們談談載入圖片的流程,項目中的該模塊處理流程如下:
1.在UI主線程中,從內存緩存中獲取圖片,找到後返回。找不到進入下一步;
2.在工作線程中,從磁碟緩存中獲取圖片,找到即返回並更新內存緩存。找不到進入下一步;
3.在工作線程中,從網路中獲取圖片,找到即返回並同時更新內存緩存和磁碟緩存。找不到顯示默認以提示。
二、內存緩存類(PanoMemCache)
這里使用Android提供的LruCache類,該類保存一個強引用來限制內容數量,每當Item被訪問的時候,此Item就會移動到隊列的頭部。當cache已滿的時候加入新的item時,在隊列尾部的item會被回收。
[java] view plain print ?
public class PanoMemoryCache {
// LinkedHashMap初始容量
private static final int INITIAL_CAPACITY = 16;
// LinkedHashMap載入因子
private static final int LOAD_FACTOR = 0.75f;
// LinkedHashMap排序模式
private static final boolean ACCESS_ORDER = true;
// 軟引用緩存
private static LinkedHashMap<String, SoftReference<Bitmap>> mSoftCache;
// 硬引用緩存
private static LruCache<String, Bitmap> mLruCache;
public PanoMemoryCache() {
// 獲取單個進程可用內存的最大值
// 方式一:使用ActivityManager服務(計量單位為M)
/*int memClass = ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryClass();*/
// 方式二:使用Runtime類(計量單位為Byte)
final int memClass = (int) Runtime.getRuntime().maxMemory();
// 設置為可用內存的1/4(按Byte計算)
final int cacheSize = memClass / 4;
mLruCache = new LruCache<String, Bitmap>(cacheSize) {
@Override
protected int sizeOf(String key, Bitmap value) {
if(value != null) {
// 計算存儲bitmap所佔用的位元組數
return value.getRowBytes() * value.getHeight();
} else {
return 0;
}
}
@Override
protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
if(oldValue != null) {
// 當硬引用緩存容量已滿時,會使用LRU演算法將最近沒有被使用的圖片轉入軟引用緩存
mSoftCache.put(key, new SoftReference<Bitmap>(oldValue));
}
}
};
/*
* 第一個參數:初始容量(默認16)
* 第二個參數:載入因子(默認0.75)
* 第三個參數:排序模式(true:按訪問次數排序;false:按插入順序排序)
*/
mSoftCache = new LinkedHashMap<String, SoftReference<Bitmap>>(INITIAL_CAPACITY, LOAD_FACTOR, ACCESS_ORDER) {
private static final long serialVersionUID = 7237325113220820312L;
@Override
protected boolean removeEldestEntry(Entry<String, SoftReference<Bitmap>> eldest) {
if(size() > SOFT_CACHE_SIZE) {
return true;
}
return false;
}
};
}
/**
* 從緩存中獲取Bitmap
* @param url
* @return bitmap
*/
public Bitmap getBitmapFromMem(String url) {
Bitmap bitmap = null;
// 先從硬引用緩存中獲取
synchronized (mLruCache) {
bitmap = mLruCache.get(url);
if(bitmap != null) {
// 找到該Bitmap之後,將其移到LinkedHashMap的最前面,保證它在LRU演算法中將被最後刪除。
mLruCache.remove(url);
mLruCache.put(url, bitmap);
return bitmap;
}
}
// 再從軟引用緩存中獲取
synchronized (mSoftCache) {
SoftReference<Bitmap> bitmapReference = mSoftCache.get(url);
if(bitmapReference != null) {
bitmap = bitmapReference.get();
if(bitmap != null) {
// 找到該Bitmap之後,將它移到硬引用緩存。並從軟引用緩存中刪除。
mLruCache.put(url, bitmap);
mSoftCache.remove(url);
return bitmap;
} else {
mSoftCache.remove(url);
}
}
}
return null;
}
/**
* 添加Bitmap到內存緩存
* @param url
* @param bitmap
*/
public void addBitmapToCache(String url, Bitmap bitmap) {
if(bitmap != null) {
synchronized (mLruCache) {
mLruCache.put(url, bitmap);
}
}
}
/**
* 清理軟引用緩存
*/
public void clearCache() {
mSoftCache.clear();
mSoftCache = null;
}
}
補充一點,由於4.0平台以後對SoftReference類引用的對象調整了回收策略,所以該類中的軟引用緩存實際上沒什麼效果,可以去掉。2.3以前平台建議保留。
三、磁碟緩存類(PanoDiskCache)
五、使用decodeByteArray()還是decodeStream()?
講到這里,有童鞋可能會問我為什麼使用BitmapFactory.decodeByteArray(data, 0, data.length, opts)來創建Bitmap,而非使用BitmapFactory.decodeStream(is, null, opts)。你這樣做不是要多寫一個靜態方法readInputStream()嗎?
沒錯,decodeStream()確實是該使用情景下的首選方法,但是在有些情形下,它會導致圖片資源不能即時獲取,或者說圖片被它偷偷地緩存起來,交 還給我們的時間有點長。但是延遲性是致命的,我們等不起。所以在這里選用decodeByteArray()獲取,它直接從位元組數組中獲取,貼近於底層 IO、脫離平台限制、使用起來風險更小。
六、引入緩存機制後獲取圖片的方法
[java] view plain print ?
/**
* 載入Bitmap
* @param url
* @return
*/
private Bitmap loadBitmap(String url) {
// 從內存緩存中獲取,推薦在主UI線程中進行
Bitmap bitmap = memCache.getBitmapFromMem(url);
if(bitmap == null) {
// 從文件緩存中獲取,推薦在工作線程中進行
bitmap = diskCache.getBitmapFromDisk(url);
if(bitmap == null) {
// 從網路上獲取,不用推薦了吧,地球人都知道~_~
bitmap = PanoUtils.downloadBitmap(this, url);
if(bitmap != null) {
diskCache.addBitmapToCache(bitmap, url);
memCache.addBitmapToCache(url, bitmap);
}
} else {
memCache.addBitmapToCache(url, bitmap);
}
}
return bitmap;
}
七、工作線程池化
有關多線程的切換問題以及在UI線程中執行loadBitmap()方法無效的問題,請參見另一篇博文: 使用嚴苛模式打破Android4.0以上平台應用中UI主線程的「獨斷專行」。
有關工作線程的處理方式,這里推薦使用定製線程池的方式,核心代碼如下:
[java] view plain print ?
// 線程池初始容量
private static final int POOL_SIZE = 4;
private ExecutorService executorService;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 獲取當前使用設備的CPU個數
int cpuNums = Runtime.getRuntime().availableProcessors();
// 預開啟線程池數目
executorService = Executors.newFixedThreadPool(cpuNums * POOL_SIZE);
...
executorService.submit(new Runnable() {
// 此處執行一些耗時工作,不要涉及UI工作。如果遇到,直接轉交UI主線程
pano.setImage(loadBitmap(url));
});
...
}
我們知道,線程構造也是比較耗資源的。一定要對其進行有效的管理和維護。千萬不要隨意而行,一張圖片的工作線程不搭理也許沒什麼,當使用場景變為 ListView和GridView時,線程池化工作就顯得尤為重要了。Android不是提供了AsyncTask嗎?為什麼不用它?其實 AsyncTask底層也是靠線程池支持的,它默認分配的線程數是128,是遠大於我們定製的executorService。