Android面試集錦系列(42)——SQLite資料庫
前言
鷺島廈門是個很美麗的海濱城市,給我的感覺很舒適和悠閒,據說政府對到那工作的高新技術人才第一年有10萬元的獎勵,因為這個原因我很有興趣的參加了一個廈門公司的面試。他們主要是研發VOIP方面的技術,對手機應用的效能優化和音訊演算法有較高的要求。
他們招人的薪資半年內漲了30萬元,都一直都沒有招到合適的人。為什麼呢?因為他們要求技術好的同時,英語也要好。什麼才叫好呢?就是可以用流利的英語和國外的團隊無障礙交流。
這就為難很多程式設計師了。這裡就不再講英語的勵志故事了,我們回到技術面試上。廈門這家公司對技術的要求還是比較高,問了很多對Android機制的理解問題,為什麼面試官很在意對機制的理解呢?因為實際的專案中很多效能問題都是由於缺乏對Android執行機制的正確理解的程式設計師引發的。除了機制問題,現在印象比較深的就是關於SQLite資料庫操作的效能優化問題。
面試題:如何對SQLite資料庫中進行大量的資料插入?
Android系統內建了SQLite資料庫,並且提供了一整套的API用於對資料庫進行增刪改查操作。SQLite是一個輕量的、跨平臺的、開源的資料庫引擎。SQLite每個資料庫都是以單個檔案(.db)的形式存在,這些資料都是以B-Tree的資料結構形式儲存在磁碟上。
使用SQLiteDatabase的insert,delete等方法或者execSQL方法預設都開啟了事務,如果操作的順利完成才會更新.db資料庫。事務的實現是依賴於名為rollback journal檔案,藉助這個臨時檔案來完成原子操作和回滾功能。
大家可以在/data/data/<packageName>/databases/目錄下看到一個和資料庫同名的.db-journal檔案。
SQLite想要執行操作,需要將程式中的SQL語句編譯成對應的SQLiteStatement,比如" select * from table1 ",每執行一次都需要將這個String型別的SQL語句轉換成SQLiteStatement。如下insert的操作最終都是將ContentValues轉成SQLiteStatementi:
public long insertWithOnConflict(String table, String nullColumnHack, ContentValues initialValues, int conflictAlgorithm) { // 省略部份程式碼 SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); try { return statement.executeInsert(); } finally { statement.close(); } } finally { releaseReference(); } }
對於批量處理插入或者更新的操作,我們可以重用SQLiteStatement,使用SQLiteDatabase的beginTransaction()方法開啟一個事務,樣例如下:
try { sqLiteDatabase.beginTransaction(); SQLiteStatement stat = sqLiteDatabase.compileStatement(insertSQL); // 插入10000次 for (int i = 0; i < 10000; i++) { stat.bindLong(1, 123456); stat.bindString(2, "test"); stat.executeInsert(); } sqLiteDatabase.setTransactionSuccessful(); } catch (SQLException e) { e.printStackTrace(); } finally { // 結束 sqLiteDatabase.endTransaction(); sqLiteDatabase.close(); }
我在華為Nexus 6P上對常見的幾種做法做了一下測試。
直接使用SQL語句進行插入
直接使用SQL語句插入,新增事務
使用ContentValues方式,新增事務
使用SQLiteStatement方式,新增事務
結果如下圖:

從資料上看,第四種方式使用SQLiteStatement最快,不過只要添加了事務(或者說只需要一個事務,不是每條插入都使用事務),後三種方式的差別並不大。所以針過這個題目的插入的優化可以通過“SQLiteStatement+事務”的方式顯著提高效率。
查詢方面的優化一般可以通過建立索引。建立索引會對插入和更新的操作效能產生影響,使用索引需要考慮實際情況進行利弊權衡,對於查詢操作量級較大,業務對要求查詢要求較高的,還是推薦使用索引。所以這會有一個取捨問題,看你的專案是查詢頻繁還是插入和修改頻繁。當然還有一些小的優化細節,如果面試官問到也可以說幾點(如limit)。
執行緒問題
SQLite的同步鎖精確到資料庫級,粒度比較大,不像別的資料庫有表鎖,行鎖。同一個時間只允許一個連線進行寫入操作。
如果有大量的資料處理,那麼肯定不合適於在UI執行緒去操作,這時就要考慮多執行緒的問題了。我們如果開一個工作執行緒去操作SQLite資料庫,如批量地插入可能需要30秒鐘,而這個時間UI執行緒也要從資料庫讀取一下資料展示給使用者,那麼這個時候UI執行緒能讀取到這個資料庫嗎?大家可以思考一下這個問題。
我們常常在多執行緒中只使用一個SQLiteDatabase引用,在用SQLiteDataBase.close()的時需要注意調是否還有別的執行緒在使用這個例項。如果一個執行緒操作完成後就直接close了,別一個正在使用這個資料庫的執行緒就會異常。所以有些人會直接把SQLiteDatabase的例項放在Application中,讓它們的生命週期一致。也有的做法是寫一個計數器,當計數器為0時才真正關閉資料庫。
使用ORM的問題
目前網上有很多開源的ORM(物件關係資料對映)框架,如greenDAO、ormlite等等。在使用這些框架有必要很瞭解一下它們的利弊,特別是一些使用反射的框架,對效能的影響會比較大。有些框架在多執行緒同步方面也會產生一些問題,所以使用時要有所顧慮。
Realm 是最近興起的一個專注於移動裝置資料庫的庫,其核心是使用C++編寫,號稱很多時候資料的存取速度比SQLite要快很多。不過在我的一些專案中,發現它讀取並不比SQLite快。
小結
在實踐中我們總結出一條守則:“不要用Helloworld來測試自己的框架(或程式碼),要測就要用真實的資料和環境。”
特別是針對資料庫方面,如果只用幾條簡單的資料進行測式,那麼你會很容易傲嬌和滿足,而忽視了很多問題。沒有經過真實資料(或大量資料)測試之前,不要對自己的程式碼太過自信。
最後
在現在這個金三銀四的面試季,我自己在網上也蒐集了很多資料做成了文件和架構視訊資料免費分享給大家【 包括高階UI、效能優化、架構師課程、NDK、Kotlin、混合式開發(ReactNative+Weex)、Flutter等架構技術資料 】,希望能幫助到您面試前的複習且找到一個好的工作,也節省大家在網上搜索資料的時間來學習。
資料獲取方式:加入Android架構交流QQ群聊:513088520 ,進群即領取資料!!!
點選連結加入群聊【Android移動架構總群】:加入群聊

資料大全