第 6 章 數(shù)據(jù)存儲(chǔ)全方案,詳解持久化技術(shù)
一:文件存儲(chǔ)
- 將數(shù)據(jù)存儲(chǔ)到文件中(使用 Java 流的方式將數(shù)據(jù)寫入到文件中)
- 這里通過(guò)openFileOutput()方法能夠得到一個(gè) FileOutputStream 對(duì)象
- 然后再借助它構(gòu)建出一個(gè) OutputStreamWriter 對(duì)象
- 接著再使用 OutputStreamWriter 構(gòu)建出一個(gè) BufferedWriter 對(duì)象
- 這樣你就可以通過(guò) BufferedWriter.write 來(lái)將文本內(nèi)容寫入到文件中了
- 關(guān)閉文件流
public void save() {
String data = "Data to save";
FileOutputStream out = null;
BufferedWriter writer = null;
try {
out = openFileOutput("data", Context.MODE_PRIVATE); // 1 FileOutputStream
writer = new BufferedWriter(new OutputStreamWriter(out)); // 2 BufferedWriter葵腹、3 BufferedWriter
writer.write(data); // 4
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close(); // 5
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 從文件中讀取數(shù)據(jù)
- 首先通過(guò) openFileInput()方法獲取到了一個(gè) FileInputStream 對(duì)象忙菠,
- 然后借助它又構(gòu)建出了一個(gè) InputStreamReader 對(duì)象嗡午,
- 接著再使用 InputStreamReader 構(gòu)建出一個(gè) BufferedReader 對(duì)象坏瞄,
- 這樣我們就可以通過(guò) BufferedReader 進(jìn)行一行行地讀取仁堪,把文件中所有的文本內(nèi)容全部讀取出來(lái)并存放在一個(gè)StringBuilder對(duì)象中媳纬,
- 關(guān)閉文件流
- 最后將讀取到的內(nèi)容返回就可以了
public String load() {
FileInputStream in = null;
BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try {
in = openFileInput("data"); //1 FileInputStream
reader = new BufferedReader(new InputStreamReader(in)); // 2、3
String line = "";
while ((line = reader.readLine()) != null) { //4
content.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close(); // 5
}
catch (IOException e) {
e.printStackTrace();
}
}
}
return content.toString();
}
二:SharedPreferences 存儲(chǔ)
-
將數(shù)據(jù)存儲(chǔ)到 SharedPreferences 中
- 首先需要獲取到SharedPreferences對(duì)象(有三種方法可以獲任苹恰)
- Context類中的 getSharedPreferences()方法
- Activity類中的 getPreferences()方法(這個(gè)方法時(shí)會(huì)自動(dòng)將當(dāng)前活動(dòng)的類名作為 SharedPreferences的文件名)
- PreferenceManager類中的 getDefaultSharedPreferences()方法(這是一個(gè)靜態(tài)方法帖烘,它接收一個(gè)Context參數(shù),并自動(dòng)使用當(dāng)前應(yīng)用程序的包名作為前綴來(lái)命名 SharedPreferences文件)
- 調(diào)用 SharedPreferences對(duì)象的 edit()方法來(lái)獲取一個(gè) SharedPreferences.Editor 對(duì)象橄杨。
- 向 SharedPreferences.Editor 對(duì)象中添加數(shù)據(jù)秘症,比如添加一個(gè)布爾型數(shù)據(jù)就使用putBoolean 方法
- 調(diào)用 commit() 或apply() 方法將添加的數(shù)據(jù)提交,從而完成數(shù)據(jù)存儲(chǔ)操作式矫。
- 首先需要獲取到SharedPreferences對(duì)象(有三種方法可以獲任苹恰)
-
從 SharedPreferences 中讀取數(shù)據(jù)
- 首先通過(guò) getSharedPreferences()方法得到 SharedPreferences 對(duì)象乡摹,
- 然后分別調(diào)用它的 getString()、getInt()和getBoolean()方法去獲取前面所存儲(chǔ)的數(shù)據(jù)采转,如果沒(méi)有找到相應(yīng)的值就會(huì)使用方法中傳入的默認(rèn)值來(lái)代替
三:SQLite 數(shù)據(jù)庫(kù)存儲(chǔ)
- 創(chuàng)建數(shù)據(jù)庫(kù)
getReadableDatabase() 和getWritableDatabase()聪廉。這兩個(gè)方法都可以創(chuàng)建或打開一個(gè)現(xiàn)有的數(shù)據(jù)庫(kù)(如果數(shù)據(jù)庫(kù)已存在則直接打開,否則創(chuàng)建一個(gè)新的數(shù)據(jù)庫(kù)) 氏义,并返回一個(gè)可對(duì)數(shù)據(jù)庫(kù)進(jìn)行讀寫操作的對(duì)象锄列。不同的是,當(dāng)數(shù)據(jù)庫(kù)不可寫入的時(shí)候(如磁盤空間已滿)getReadableDatabase()方法返回的對(duì)象將以只讀的方式去打開數(shù)據(jù)庫(kù)惯悠,而 getWritableDatabase()方法則將出現(xiàn)異常。- 新建自定義的數(shù)據(jù)庫(kù)類 MyDatabaseHelper 繼承 SQLiteOpenHelper
public class MyDatabaseHelper extends SQLiteOpenHelper {
private Context mContext;
//使用了 primary key將 id 列設(shè)為主鍵竣况,并用 autoincrement關(guān)鍵字表示 id列是自增長(zhǎng)的克婶。
public static final String CREATE_BOOK = "create table book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
public MyDatabaseHelper(Context context, String name, CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
// 創(chuàng)建數(shù)據(jù)庫(kù)
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
2. 在需要?jiǎng)?chuàng)建數(shù)據(jù)庫(kù)時(shí)構(gòu)建一個(gè) MyDatabaseHelper對(duì)象,并且通過(guò)構(gòu)造函數(shù)的參數(shù)將數(shù)據(jù)庫(kù)名指定為 BookStore.db丹泉,版本號(hào)指定為1
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
3. 然后在 Create database 按鈕的點(diǎn)擊事件里調(diào)用了getWritableDatabase()方法情萤。
dbHelper.getWritableDatabase();
(這樣當(dāng)?shù)谝淮吸c(diǎn)擊 Create database按鈕時(shí),就會(huì)檢測(cè)到當(dāng)前程序中并沒(méi)有BookStore.db這個(gè)數(shù)據(jù)庫(kù)摹恨,于是會(huì)創(chuàng)建該數(shù)據(jù)庫(kù)并調(diào)用MyDatabaseHelper中的 onCreate()方法筋岛,這樣 Book表也就得到了創(chuàng)建,再次點(diǎn)擊 Create database按鈕時(shí)晒哄,會(huì)發(fā)現(xiàn)此時(shí)已經(jīng)存在BookStore.db數(shù)據(jù)庫(kù)了睁宰,因此不會(huì)再創(chuàng)建)
注:adb是 Android SDK中自帶的一個(gè)調(diào)試工具肪获,使用這個(gè)工具可以直接對(duì)連接在電腦上的手機(jī)或模擬器進(jìn)行調(diào)試操作。它存放在sdk的platform-tools 目錄下柒傻,如果想要在命令行中使用這個(gè)工具孝赫,就需要先把它的路徑配置到環(huán)境變量里。(輸入adbshell红符,就會(huì)進(jìn)入到設(shè)備的控制臺(tái)進(jìn)行數(shù)據(jù)庫(kù)操作)
-
升級(jí)數(shù)據(jù)庫(kù)
- 數(shù)據(jù)庫(kù)類中定義新的表格
public static final String CREATE_CATEGORY = "create table Category (" + "id integer primary key autoincrement, " + "category_name text, " + "category_code integer)";
- 在數(shù)據(jù)庫(kù)類的onCreate方法中添加新建數(shù)據(jù)表命令
db.execSQL(CREATE_CATEGORY);
- 在數(shù)據(jù)庫(kù)類的onUpgrade方法中添加命令 (刪除已存在的表格青柄,再新建)
db.execSQL("drop table if exists Book"); db.execSQL("drop table if exists Category"); onCreate(db);
- 在代碼中需要更新數(shù)據(jù)庫(kù)的地方調(diào)用以下代碼(記得升級(jí)版本號(hào))
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2); dbHelper.getWritableDatabase();
-
添加數(shù)據(jù)(getReadableDatabase()或getWritableDatabase()方法都會(huì)返回一個(gè)SQLiteDatabase對(duì)象,借助這個(gè)對(duì)象就可以對(duì)數(shù)據(jù)進(jìn)行添加预侯、刪除致开、更新等操作)
- 我們先獲取到了 SQLiteDatabase 對(duì)象,
- 然后使用ContentValues來(lái)對(duì)要添加的數(shù)據(jù)進(jìn)行組裝
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
// 開始組裝第一條數(shù)據(jù)
values.put("name", "The Da Vinci Code");
values.put("author", "Dan Brown");
values.put("pages", 454);
values.put("price", 16.96);
db.insert("Book", null, values); // 插入第一條數(shù)據(jù)
values.clear();
// 開始組裝第二條數(shù)據(jù)
values.put("name", "The Lost Symbol");
values.put("author", "Dan Brown");
values.put("pages", 510);
values.put("price", 19.95);
db.insert("Book", null, values); //
- 更新數(shù)據(jù)
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
//第三個(gè)參數(shù)對(duì)應(yīng)的是 SQL 語(yǔ)句的where部分萎馅,表示去更新所有name等于?的行双戳,而?是一個(gè)占位符,可以通過(guò)第四個(gè)參數(shù)提供的一個(gè)字符串?dāng)?shù)組為第三個(gè)參數(shù)中的每個(gè)占位符指定相應(yīng)的內(nèi)容
db.update("Book", values, "name = ?", new String[] { "The Da Vinci Code" }); //將名字是 The Da Vinci Code的這本書的價(jià)格改成 10.99
- 刪除數(shù)據(jù)
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });
- 查詢數(shù)據(jù)(Cursor對(duì)象)
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor cursor = db.query("Book", null, null, null, null, null, null);
//查詢完之后就得到了一個(gè) Cursor對(duì)象校坑,接著我們調(diào)用它的moveToFirst()方法將數(shù)據(jù)的指針移動(dòng)到第一行的位置拣技,然后進(jìn)入了一個(gè)循環(huán)當(dāng)中,去遍歷查詢到的每一行數(shù)據(jù)
if (cursor.moveToFirst()) {
do {
// 遍歷Cursor 對(duì)象耍目,取出數(shù)據(jù)并打印
String name = cursor.getString(cursor.getColumnIndex("name"));
String author = cursor.getString(cursor.getColumnIndex("author"));
int pages = cursor.getInt(cursor.getColumnIndex("pages"));
double price = cursor.getDouble(cursor.getColumnIndex("price"));
Log.d("MainActivity", "book name is " + name);
Log.d("MainActivity", "book author is " + author);
Log.d("MainActivity", "book pages is " + pages);
Log.d("MainActivity", "book price is " + price);
} while (cursor.moveToNext());
}
cursor.close();
}
-
*使用 SQL 操作數(shù)據(jù)庫(kù)(直接使用sql語(yǔ)句實(shí)現(xiàn)以上操作)
- 添加數(shù)據(jù)的方法如下:
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", new String[] { "The Da Vinci Code", "Dan Brown", "454", "16.96" }); db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", new String[] { "The Lost Symbol", "Dan Brown", "510", "19.95" });
- 更新數(shù)據(jù)的方法如下:
db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99","The Da Vinci Code" });
- 刪除數(shù)據(jù)的方法如下:
db.execSQL("delete from Book where pages > ?", new String[] { "500" });
- 查詢數(shù)據(jù)的方法如下:
db.rawQuery("select * from Book", null);
- SQLite 數(shù)據(jù)庫(kù)的最佳實(shí)踐
- 使用事務(wù)(事務(wù)的特性可以保證讓某一系列的操作要么全部完成膏斤,要么一個(gè)都不會(huì)完成。)
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction(); // 開啟事務(wù)
try {
//刪除數(shù)據(jù)
db.delete("Book", null, null);
if (true) {
// 在這里手動(dòng)拋出一個(gè)異常邪驮,讓事務(wù)失敗莫辨,此時(shí)異常被catch捕獲,無(wú)法執(zhí)行之后的添加數(shù)據(jù)操作毅访,由于事務(wù)的存在沮榜,導(dǎo)致之前的刪除操作也會(huì)被還原,所以無(wú)法刪除喻粹。
throw new NullPointerException();
}
//添加數(shù)據(jù)
ContentValues values = new ContentValues();
values.put("name", "Game of Thrones");
values.put("author", "George Martin");
values.put("pages", 720);
values.put("price", 20.85);
db.insert("Book", null, values);
db.setTransactionSuccessful(); // 事務(wù)已經(jīng)執(zhí)行成功
} catch (Exception e) {
e.printStackTrace();
} finally {//是否成功都會(huì)執(zhí)行
db.endTransaction(); // 結(jié)束事務(wù)
}
- 升級(jí)數(shù)據(jù)庫(kù)的最佳寫法(為每一個(gè)版本賦予它各自改變的內(nèi)容蟆融,然后在onUpgrade()方法中對(duì)當(dāng)前數(shù)據(jù)庫(kù)的版本號(hào)進(jìn)行判斷,再執(zhí)行相應(yīng)的改變)
//需求1:向數(shù)據(jù)庫(kù)中再添加一張 Category表
- 在 onCreate()方法里我們新增了一條建表語(yǔ)句守呜,
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY); //新增了一條建表語(yǔ)句
}
- 然后又在 onUpgrade()方法中添加了一個(gè)switch判斷型酥,如果用戶當(dāng)前數(shù)據(jù)庫(kù)的版本號(hào)是1,就只會(huì)創(chuàng)建一張Category表查乒。這樣當(dāng)用戶是直接安裝的第二版的程序時(shí)弥喉,就會(huì)將兩張表一起創(chuàng)建。而當(dāng)用戶是使用第二版的程序覆蓋安裝第一版的程序時(shí)玛迄,就會(huì)進(jìn)入到升級(jí)數(shù)據(jù)庫(kù)的操作中由境,此時(shí)由于 Book 表已經(jīng)
存在了,因此只需要?jiǎng)?chuàng)建一張 Category表即可蓖议。
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
default:
}
}
//需求2:需要在 Book表中添加一個(gè) category_id 的字段
- 修改原先的建表語(yǔ)句虏杰,添加一個(gè) category_id 的字段
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text, "
+ "category_id integer)";
- 修改onUpgrade方法
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
case 2:
db.execSQL("alter table Book add column category_id integer");
default:
}
}
注:
- switch 中每一個(gè) case 的最后都是沒(méi)有使用break的讥蟆,為什么要這么做呢?這是為了保證在跨版本升級(jí)的時(shí)候嘹屯,每一次的數(shù)據(jù)庫(kù)修改都能被全部執(zhí)行到攻询。
- 使用這種方式來(lái)維護(hù)數(shù)據(jù)庫(kù)的升級(jí),不管版本怎樣更新州弟,都可以保證數(shù)據(jù)庫(kù)的表結(jié)構(gòu)是最新的钧栖,而且表中的數(shù)據(jù)也完全不會(huì)丟失了。