Flutter的鍵值存儲數(shù)據(jù)庫使用示例詳解
Flutter 鍵值存儲數(shù)據(jù)庫
鍵值存儲是開發(fā)中十分常見的需求,在Flutter開發(fā)中,一般使用 shared_preferences 插件來實現(xiàn)。shared_preferences 本質(zhì)上就是將鍵值對保存到一個XML文件中進行持久化。
而shared_preferences 實際上存在一定缺陷,譬如其性能較差,不適合處理大量數(shù)據(jù),不能創(chuàng)建新的XML文件,所有數(shù)據(jù)存在同一個文件中。除此外,還有其他一些持久化方案,如SQLite、Hive等。
SQLite是關(guān)系型數(shù)據(jù)庫,使用起來相對繁瑣;而Hive是用Dart實現(xiàn)的一個輕量級鍵值對數(shù)據(jù)庫,它使用簡單,但同樣性能較差,而且在儲存大量數(shù)據(jù)時,更加耗費內(nèi)存,因為它是一次性將所有數(shù)據(jù)讀取到內(nèi)存中,這在移動端不是很可取。當然,如果你只是用來存儲幾個簡單的配置項數(shù)據(jù),那也夠用了。
unqlite
以上概述了一下Flutter的鍵值持久化方案,那么接下來就隆介紹一下本文推薦的Flutter鍵值存儲方案——一個輕量級嵌入式nosql數(shù)據(jù)庫unqlite!
unqlite是一個嵌入式的數(shù)據(jù)庫,它實現(xiàn)了一個獨立的、無服務(wù)器、零配置、事務(wù)性的nosql數(shù)據(jù)庫引擎。它是一個文檔存儲數(shù)據(jù)庫,類似于MongoDB, Redis, CouchDB等,同時也是一個標準的key/value存儲數(shù)據(jù)庫,類似于BerkeleyDB, LevelDB, 等。
- 首先unqlite是一個無服務(wù)器的數(shù)據(jù)庫,這意味著它的通訊是直接讀取數(shù)據(jù)庫文件的(從磁盤讀?。?,沒有中間服務(wù)器進程。
- 然后unqlite也不像其他服務(wù)器一樣,需要安裝、配置以及權(quán)限管理等。它是直接嵌入到我們的程序中的
- unqlite是一個單一數(shù)據(jù)庫文件,它的數(shù)據(jù)庫是一個普通的磁盤文件。這就意味著你可以把它隨意的復制或備份到其他地方。
- unqlite文件格式是跨平臺的。在一臺機器上編寫的數(shù)據(jù)庫文件可以復制到具有不同體系結(jié)構(gòu)的不同機器上并在其上使用。
- unqlite是一個標準的鍵/值存儲數(shù)據(jù)庫,類似于BerkeleyDB,Tokyo Cabinet,LevelDB等,但具有豐富的功能集,包括對事務(wù)的支持(ACID)。在KV存儲下,鍵和值都被視為簡單的字節(jié)數(shù)組,因此內(nèi)容可以是ASCII字符串,二進制blob甚至磁盤文件。
簡單概括,unqlite是一個標準C語言實現(xiàn)的輕量級、高性能nosql數(shù)據(jù)庫,經(jīng)過編譯后,它只有幾百KB大小,可以嵌入到我們的App中,它與SQLite數(shù)據(jù)庫類似,區(qū)別在于一個是基于SQL的關(guān)系型數(shù)據(jù)庫,一個是NoSql數(shù)據(jù)庫。
unqlite_flutter
既然unqlite是一個C語言編寫的數(shù)據(jù)庫,那么Flutter開發(fā)如何使用它呢?
隨著Dart版本的不斷迭代,Dart語言的FFI接口逐漸成熟,F(xiàn)FI類似于Java的JNI,賦予了Dart語言直接調(diào)用C語言的能力。有了這種能力,那么基于C/C++開發(fā)的優(yōu)秀的高性能的庫,都可以用于Flutter開發(fā)中,可以說是C/C++生態(tài)為我所用!想學習Flutter的FFI開發(fā),可以參考博主的B站視頻 Dart FFI開發(fā)入門 以及 程序員的C
當然,這里我已經(jīng)完成了Dart FFI調(diào)用的封裝,可以直接依賴我開發(fā)好的插件庫——unqlite_flutter
目前已完成的功能:
- key-value 儲存
- JSON 文檔儲存
快速上手
簡單鍵值對存儲
添加依賴:
unqlite: ^0.0.3 unqlite_flutter: ^0.0.3
示例代碼:
// 創(chuàng)建或打開一個數(shù)據(jù)庫 UnQLite db = UnQLite.open("${appDocDir.path}/test.db"); // 保存鍵值對 db.store("name", "Alex"); db.store("age", 18); db.store(19, "haha"); // 通過指定泛型獲取值 debugPrint(db.fetch<String>("name")); debugPrint('${db.fetch<int>("age")}'); debugPrint(db.fetch<String>(19)); // 另一種獲取值的方法 db.fetchCallback<int>("age", (val) { debugPrint('age=$val'); });
當然,還有另一種獲取數(shù)據(jù)的方式,可能比fetch
更快:
var cursor = db.cursor(); cursor.seek('name'); debugPrint('=> ${cursor.key} => ${cursor.value}');
你還可以使用事務(wù)。在一個事務(wù)中,如果發(fā)生異常,你可以回滾所有操作:
var trans = db.transaction().begin(); try { for (var i = 0; i < 100000; i++) { if (i == 10) { // 這里我們拋出一個異常 throw Exception('test'); } db.store("transaction_$i", "here is a transaction_$i"); } trans.commit(); } catch (e) { // 處理異常,手動回滾事務(wù) trans.rollback(); }
以上示例中,我們保存十萬個數(shù)據(jù),一旦中間發(fā)生了什么異常,我們執(zhí)行回滾操作,則前面所有的保存的數(shù)據(jù)將被取消。
你還可以使用迭代器來進行遍歷:
for (var entry in db.cursor()) { var content = '${entry.key} => ${entry.value}'; debugPrint(content); }
JSON
處理JSON文檔
UnQLite db = UnQLite.open("${appDocDir.path}/test2.db"); var users = db.collection("users"); // Create a collection users.create(); // 保存JSON users.store(jsonDecode(''' { "title": "test json string", "author": [ "arcticfox1919" ], "year": 2022, "like": "flutter" } ''')); users.store({'name': 'Mickey', 'age': 17}); users.store([ {'name': 'Alice', 'age': 18}, {'name': 'Bruce', 'age': 19}, {'name': 'Charlie', 'age': 20}, ]); // 獲取全部數(shù)據(jù) print(users.all()); // print(users.fetch(0)); // print(users.fetch(1)); // print(users.fetch(2)); // print(users.errorLog()); print(users.creationDate()); print(users.len()); print(users.fetchCurrent()); // 刪除全部 users.drop(); db.close();
為什么你應(yīng)該使用unqlite_flutter?
- 比 Hive 更快,占用更少的內(nèi)存
- 可以支持 JSON 文檔
它有什么缺點? 因為使用了dart ffi,所以不能支持Flutter web。
下面是一些性能測試數(shù)據(jù),這里沒有列出內(nèi)存占用的百分比,但可以肯定 unqlite 比Hive 使用了更少的內(nèi)存:
UnQLite:
UnQLite init:1 ms write 100,000 entries :611 ms fetch 100,000 entries :370 ms seek 100,000 entries :215 ms iterate 100,000 entries :225 ms transaction rollback :39 ms
Hive:
Hive init:48 ms put 100,000 entries :807 ms get 100,000 entries :290 ms
這是用于測試的代碼,兩者都在同一部真機上以profile模式運行:
testUnQLite() async { var appDocDir = await getApplicationDocumentsDirectory(); final start = DateTime.now().millisecondsSinceEpoch; UnQLite db = UnQLite.open("${appDocDir.path}/test.db"); final t1 = DateTime.now().millisecondsSinceEpoch; for (var i = 0; i < 100000; i++) { db.store("my_key_$i", "Here is a value for testing—$i"); } final t2 = DateTime.now().millisecondsSinceEpoch; for (var i = 0; i < 100000; i++) { var r = db.fetch<String>("my_key_$i"); // debugPrint("fetch :$r"); } final t3 = DateTime.now().millisecondsSinceEpoch; var cursor = db.cursor(); for (var i = 0; i < 100000; i++) { cursor.seek('my_key_$i'); // debugPrint('=> ${cursor.key} => ${cursor.value}'); } final t4 = DateTime.now().millisecondsSinceEpoch; var count = 0; for (var entry in db.cursor()) { count++; var content = '${entry.key} => ${entry.value}'; // debugPrint(content); } print('count => $count'); final t5 = DateTime.now().millisecondsSinceEpoch; var trans = db.transaction().begin(); try { for (var i = 0; i < 100000; i++) { if (i == 10) { throw Exception('test'); } db.store("transaction_$i", "here is a transaction_$i"); } trans.commit(); } catch (e) { trans.rollback(); } final t6 = DateTime.now().millisecondsSinceEpoch; debugPrint("UnQLite init:${t1-start} ms"); debugPrint("write 100,000 entries :${t2-t1} ms"); debugPrint("fetch 100,000 entries :${t3-t2} ms"); debugPrint("seek 100,000 entries :${t4-t3} ms"); debugPrint("iterate 100,000 entries :${t5-t4} ms"); debugPrint("transaction rollback :${t6-t5} ms"); db.close(); } testHive() async { var appDocDir = await getApplicationDocumentsDirectory(); var path = appDocDir.path; final start = DateTime.now().millisecondsSinceEpoch; Hive.init(path); var box = await Hive.openBox('testBox'); final t1 = DateTime.now().millisecondsSinceEpoch; for (var i = 0; i < 100000; i++) { box.put("my_key_$i", "here is a transaction_$i"); } final t2 = DateTime.now().millisecondsSinceEpoch; for (var i = 0; i < 100000; i++) { var name = box.get('my_key_$i'); } final t3 = DateTime.now().millisecondsSinceEpoch; box.close(); debugPrint("Hive init:${t1-start} ms"); debugPrint("put 100,000 entries :${t2-t1} ms"); debugPrint("get 100,000 entries :${t3-t2} ms"); }
以上就是Flutter的鍵值存儲數(shù)據(jù)庫使用示例詳解的詳細內(nèi)容,更多關(guān)于Flutter鍵值存儲數(shù)據(jù)庫的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android開發(fā)中MotionEvent坐標獲取方法分析
這篇文章主要介紹了Android開發(fā)中MotionEvent坐標獲取方法,結(jié)合實例形式分析了MotionEvent獲取坐標的相關(guān)函數(shù)使用方法與相關(guān)注意事項,需要的朋友可以參考下2016-02-02Android設(shè)置Activity背景為透明style的簡單方法(必看)
下面小編就為大家?guī)硪黄狝ndroid設(shè)置Activity背景為透明style的簡單方法(必看)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-10-10深入android中The connection to adb is down的問題以及解決方法
本篇文章是對android中The connection to adb is down的問題以及解決方法進行了詳細的分析介紹,需要的朋友參考下2013-05-05Android網(wǎng)頁H5 Input選擇相機和系統(tǒng)相冊
這篇文章主要為大家詳細介紹了Android網(wǎng)頁H5 Input選擇相機和系統(tǒng)相冊,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-10-10Android 中ActionBar+fragment實現(xiàn)頁面導航的實例
這篇文章主要介紹了Android 中ActionBar+fragment實現(xiàn)頁面導航的實例的相關(guān)資料,希望通過本文能幫助到大家實現(xiàn)這樣的功能,需要的朋友可以參考下2017-09-09Android仿抖音右滑清屏左滑列表功能的實現(xiàn)代碼
這篇文章主要介紹了Android仿抖音右滑清屏左滑列表功能,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06