[原始碼]Sqlite是怎麼通過CursorWindow讀DB的
更多內容在這裡檢視
https://ahangchen.gitbooks.io/windy-afternoon/content/
執行QUERY
執行SQLiteDatabase類中query系列函式時,只會構造查詢資訊,不會執行查詢。
(query的原始碼追蹤路徑)
執行MOVE(裡面的FILLWINDOW是真正開啟檔案控制程式碼並分配記憶體的地方)
當執行Cursor的move系列函式時,第一次執行,會為查詢結果集建立一塊共享記憶體,即cursorwindow
moveToPosition原始碼路徑
FILLWINDOW----真正耗時的地方
然後會執行sql語句,向共享記憶體中填入資料,
fillWindow原始碼路徑
在SQLiteCursor.java中可以看到
@Override
public boolean onMove(int oldPosition, int newPosition) {
// Make sure the row at newPosition is present in the window
if (mWindow == null || newPosition < mWindow.getStartPosition() ||
newPosition >= (mWindow.getStartPosition() + mWindow.getNumRows())) {
fillWindow(newPosition);
}
return true;
}
如果請求查詢的位置在cursorWindow的範圍內,不會執行fillWindow,
而超出cursorwindow的範圍,會呼叫fillWindow,
而在nativeExecuteForCursorWindow中,
獲取記錄時,如果要請求的位置超出視窗範圍,會發生CursorWindow的清空:
CopyRowResult cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
if (cpr == CPR_FULL && addedRows && startPos + addedRows < requiredPos) {
// We filled the window before we got to the one row that we really wanted.
// Clear the window and start filling it again from here.
// TODO: Would be nicer if we could progressively replace earlier rows.
window->clear();
window->setNumColumns(numColumns);
startPos += addedRows;
addedRows = 0;
cpr = copyRow(env, window, statement, numColumns, startPos, addedRows);
}
CursorWindow的清空機制會影響到多執行緒讀(通常認為不可以併發讀寫,sqlite的併發實際上是序列執行的,但可以併發讀,這裡要強調的是多執行緒讀也可能有問題),具體見稍後一篇文章“listview併發讀寫資料庫”。
上面說的這些直觀的感受是什麼樣的呢?大概是這樣,
執行query,讀10000條資料,很快就拿到了cursor,這裡不會卡,
執行moveToFirst,卡一下(fillwindow(0))
moveToPosition(7500),卡一下,因為已經超了cursorwindow的區域,又去fillwindow(7500),
關於fillwindow還有一些奇特的細節,比如4.0以後,fillwindow會填充position前後各一段資料,防止讀舊資料的時候又需要fill,感興趣的同學可以看看各個版本fillwidow的原始碼。
這裡還可以延伸一下,因為高版本的android sqlite對舊版有許多改進,
所以實際開發裡我們有時候會把sqlite的原始碼帶在自己的工程裡,使得低版本的android也可以使用高版本的特性,並且避開一部分相容性問題。
CURSOR關閉(顯式呼叫CLOSE()的理由)
追蹤原始碼看關閉
//SQLiteCursor
super.close();
synchronized (this) {
mQuery.close();
mDriver.cursorClosed();
}
//AbstractCursor
public void close() {
mClosed = true;
mContentObservable.unregisterAll();
onDeactivateOrClose();
}
protected void onDeactivateOrClose() {
if (mSelfObserver != null) {
mContentResolver.unregisterContentObserver(mSelfObserver);
mSelfObserverRegistered = false;
}
mDataSetObservable.notifyInvalidated();
}
//AbstractWindowedCursor
/** @hide */
@Override
protected void onDeactivateOrClose() {
super.onDeactivateOrClose();
closeWindow();
}
protected void closeWindow() {
if (mWindow != null) {
mWindow.close();
mWindow = null;
}
}
//SQLiteClosable
public void close() {
releaseReference();
}
public void releaseReference() {
boolean refCountIsZero = false;
synchronized(this) {
refCountIsZero = --mReferenceCount == 0;
}
if (refCountIsZero) {
onAllReferencesReleased();
}
}
//CursorWindow
@Override
protected void onAllReferencesReleased() {
dispose();
}
private void dispose() {
if (mCloseGuard != null) {
mCloseGuard.close();
}
if (mWindowPtr != 0) {
recordClosingOfWindow(mWindowPtr);
nativeDispose(mWindowPtr);
mWindowPtr = 0;
}
}
跟CursorWindow有關的路徑裡,最終呼叫nativeDispose()清空cursorWindow;
當Cursor被GC回收時,會呼叫finalize:
@Override
protected void finalize() {
try {
// if the cursor hasn't been closed yet, close it first
if (mWindow != null) {
if (mStackTrace != null) {
String sql = mQuery.getSql();
int len = sql.length();
StrictMode.onSqliteObjectLeaked(
"Finalizing a Cursor that has not been deactivated or closed. " +
"database = " + mQuery.getDatabase().getLabel() +
", table = " + mEditTable +
", query = " + sql.substring(0, (len > 1000) ? 1000 : len),
mStackTrace);
}
close();
}
} finally {
super.finalize();
}
}
然而finalize()並沒有釋放CursorWindow,而super.finalize();裡也只是解綁了觀察者,沒有去釋放cursorwindow
所以不呼叫cursor.close(),最終會導致cursorWindow所在的共享記憶體(1M或2M)洩露。
相關文章
- Kitex原始碼閱讀——腳手架程式碼是如何通過命令列生成的(二)原始碼命令列
- Kitex原始碼閱讀——腳手架程式碼是如何通過命令列生成的(一)原始碼命令列
- 閱讀原始碼,通過LinkedList回顧基礎原始碼
- 怎麼閱讀原始碼【除錯觀察原始碼】原始碼除錯
- 閱讀原始碼後,來講講React Hooks是怎麼實現的原始碼ReactHook
- 【譯】通過閱讀原始碼來提高 JS 知識原始碼JS
- 不看原始碼,怎麼卷的過小年輕原始碼
- 帶你讀 MySQL 原始碼:where 條件怎麼過濾記錄?MySql原始碼
- sqlmap原始碼通讀(一)SQL原始碼
- sqlmap原始碼通讀(二)SQL原始碼
- 通過原始碼分析Mybatis的功能原始碼MyBatis
- 交友原始碼中即時通訊怎麼工作的?原始碼
- 通過程式碼解釋什麼是API,什麼是SDK?API
- Spring 原始碼(3)Spring BeanFactory 是怎麼建立的?Spring原始碼Bean
- 通過閱讀 Douglas Crockford 的原始碼學習如何寫 JSON parser(一)原始碼JSON
- 如何通過DBLINK取remote DB的DDLREM
- Calendar原始碼--JDK是怎麼計算時間的原始碼JDK
- 選redis還是memcache,原始碼怎麼說?Redis原始碼
- 工作4年多才學會怎麼去讀原始碼,可悲麼?原始碼
- Vue原始碼閱讀--過濾器Vue原始碼過濾器
- Dubbo原始碼學習之-通過原始碼看看dubbo對netty的使用原始碼Netty
- Kafka原始碼篇 --- 可能是你看過最詳細的RecordAccumulator解讀Kafka原始碼
- 想讀專案原始碼?可為什麼總是讀不下去?原始碼
- 通過.net core原始碼看下Dictionary的實現原始碼
- 從vue2.6.10原始碼看vue是怎麼跑起來的Vue原始碼
- 怎麼通過Python掙外快,通過Python掙外快的幾種方式!Python
- 背景透明的實現,直播電商原始碼是怎麼做的原始碼
- lora模組是怎麼提過廣播傳輸通訊?
- linux下通過原始碼安裝gitLinux原始碼Git
- 通過了解RejectedExecutionException來分析ThreadPoolExecutor原始碼Exceptionthread原始碼
- 訊息已讀、未讀是怎麼設計的?
- 原始碼解讀-vue是如何實現$nextTick的原始碼Vue
- 系統程式是什麼?怎麼通過系統程式進行病毒分析?
- 通過GitHub Blame深入分析Redux原始碼GithubRedux原始碼
- 通過原始碼學習@functools.lru_cache原始碼
- 【原始碼閱讀】AndPermission原始碼閱讀原始碼
- 這個原始碼是開源的麼原始碼
- Python原始碼怎麼讀,聽聽頂級爬蟲工程師的建議Python原始碼爬蟲工程師
- spring原始碼閱讀--容器啟動過程Spring原始碼