第 6 章 數據存儲全方案,詳解持久化技術
一:文件存儲
- 將數據存儲到文件中(使用 Java 流的方式將數據寫入到文件中)
- 這里通過openFileOutput()方法能夠得到一個 FileOutputStream 對象
- 然后再借助它構建出一個 OutputStreamWriter 對象
- 接著再使用 OutputStreamWriter 構建出一個 BufferedWriter 對象
- 這樣你就可以通過 BufferedWriter.write 來將文本內容寫入到文件中了
- 關閉文件流
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();
}
}
}
- 從文件中讀取數據
- 首先通過 openFileInput()方法獲取到了一個 FileInputStream 對象,
- 然后借助它又構建出了一個 InputStreamReader 對象,
- 接著再使用 InputStreamReader 構建出一個 BufferedReader 對象,
- 這樣我們就可以通過 BufferedReader 進行一行行地讀取,把文件中所有的文本內容全部讀取出來并存放在一個StringBuilder對象中,
- 關閉文件流
- 最后將讀取到的內容返回就可以了
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 存儲
-
將數據存儲到 SharedPreferences 中
- 首先需要獲取到SharedPreferences對象(有三種方法可以獲取)
- Context類中的 getSharedPreferences()方法
- Activity類中的 getPreferences()方法(這個方法時會自動將當前活動的類名作為 SharedPreferences的文件名)
- PreferenceManager類中的 getDefaultSharedPreferences()方法(這是一個靜態方法,它接收一個Context參數,并自動使用當前應用程序的包名作為前綴來命名 SharedPreferences文件)
- 調用 SharedPreferences對象的 edit()方法來獲取一個 SharedPreferences.Editor 對象。
- 向 SharedPreferences.Editor 對象中添加數據,比如添加一個布爾型數據就使用putBoolean 方法
- 調用 commit() 或apply() 方法將添加的數據提交,從而完成數據存儲操作。
- 首先需要獲取到SharedPreferences對象(有三種方法可以獲取)
-
從 SharedPreferences 中讀取數據
- 首先通過 getSharedPreferences()方法得到 SharedPreferences 對象,
- 然后分別調用它的 getString()、getInt()和getBoolean()方法去獲取前面所存儲的數據,如果沒有找到相應的值就會使用方法中傳入的默認值來代替
三:SQLite 數據庫存儲
- 創建數據庫
getReadableDatabase() 和getWritableDatabase()。這兩個方法都可以創建或打開一個現有的數據庫(如果數據庫已存在則直接打開,否則創建一個新的數據庫) ,并返回一個可對數據庫進行讀寫操作的對象。不同的是,當數據庫不可寫入的時候(如磁盤空間已滿)getReadableDatabase()方法返回的對象將以只讀的方式去打開數據庫,而 getWritableDatabase()方法則將出現異常。- 新建自定義的數據庫類 MyDatabaseHelper 繼承 SQLiteOpenHelper
public class MyDatabaseHelper extends SQLiteOpenHelper {
private Context mContext;
//使用了 primary key將 id 列設為主鍵,并用 autoincrement關鍵字表示 id列是自增長的。
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;
}
// 創建數據庫
@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. 在需要創建數據庫時構建一個 MyDatabaseHelper對象,并且通過構造函數的參數將數據庫名指定為 BookStore.db,版本號指定為1
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
3. 然后在 Create database 按鈕的點擊事件里調用了getWritableDatabase()方法。
dbHelper.getWritableDatabase();
(這樣當第一次點擊 Create database按鈕時,就會檢測到當前程序中并沒有BookStore.db這個數據庫,于是會創建該數據庫并調用MyDatabaseHelper中的 onCreate()方法,這樣 Book表也就得到了創建,再次點擊 Create database按鈕時,會發現此時已經存在BookStore.db數據庫了,因此不會再創建)
注:adb是 Android SDK中自帶的一個調試工具,使用這個工具可以直接對連接在電腦上的手機或模擬器進行調試操作。它存放在sdk的platform-tools 目錄下,如果想要在命令行中使用這個工具,就需要先把它的路徑配置到環境變量里。(輸入adbshell,就會進入到設備的控制臺進行數據庫操作)
-
升級數據庫
- 數據庫類中定義新的表格
public static final String CREATE_CATEGORY = "create table Category (" + "id integer primary key autoincrement, " + "category_name text, " + "category_code integer)";
- 在數據庫類的onCreate方法中添加新建數據表命令
db.execSQL(CREATE_CATEGORY);
- 在數據庫類的onUpgrade方法中添加命令 (刪除已存在的表格,再新建)
db.execSQL("drop table if exists Book"); db.execSQL("drop table if exists Category"); onCreate(db);
- 在代碼中需要更新數據庫的地方調用以下代碼(記得升級版本號)
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2); dbHelper.getWritableDatabase();
-
添加數據(getReadableDatabase()或getWritableDatabase()方法都會返回一個SQLiteDatabase對象,借助這個對象就可以對數據進行添加、刪除、更新等操作)
- 我們先獲取到了 SQLiteDatabase 對象,
- 然后使用ContentValues來對要添加的數據進行組裝
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
// 開始組裝第一條數據
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); // 插入第一條數據
values.clear();
// 開始組裝第二條數據
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); //
- 更新數據
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
//第三個參數對應的是 SQL 語句的where部分,表示去更新所有name等于?的行,而?是一個占位符,可以通過第四個參數提供的一個字符串數組為第三個參數中的每個占位符指定相應的內容
db.update("Book", values, "name = ?", new String[] { "The Da Vinci Code" }); //將名字是 The Da Vinci Code的這本書的價格改成 10.99
- 刪除數據
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });
- 查詢數據(Cursor對象)
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor cursor = db.query("Book", null, null, null, null, null, null);
//查詢完之后就得到了一個 Cursor對象,接著我們調用它的moveToFirst()方法將數據的指針移動到第一行的位置,然后進入了一個循環當中,去遍歷查詢到的每一行數據
if (cursor.moveToFirst()) {
do {
// 遍歷Cursor 對象,取出數據并打印
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 操作數據庫(直接使用sql語句實現以上操作)
- 添加數據的方法如下:
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" });
- 更新數據的方法如下:
db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99","The Da Vinci Code" });
- 刪除數據的方法如下:
db.execSQL("delete from Book where pages > ?", new String[] { "500" });
- 查詢數據的方法如下:
db.rawQuery("select * from Book", null);
- SQLite 數據庫的最佳實踐
- 使用事務(事務的特性可以保證讓某一系列的操作要么全部完成,要么一個都不會完成。)
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction(); // 開啟事務
try {
//刪除數據
db.delete("Book", null, null);
if (true) {
// 在這里手動拋出一個異常,讓事務失敗,此時異常被catch捕獲,無法執行之后的添加數據操作,由于事務的存在,導致之前的刪除操作也會被還原,所以無法刪除。
throw new NullPointerException();
}
//添加數據
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(); // 事務已經執行成功
} catch (Exception e) {
e.printStackTrace();
} finally {//是否成功都會執行
db.endTransaction(); // 結束事務
}
- 升級數據庫的最佳寫法(為每一個版本賦予它各自改變的內容,然后在onUpgrade()方法中對當前數據庫的版本號進行判斷,再執行相應的改變)
//需求1:向數據庫中再添加一張 Category表
- 在 onCreate()方法里我們新增了一條建表語句,
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY); //新增了一條建表語句
}
- 然后又在 onUpgrade()方法中添加了一個switch判斷,如果用戶當前數據庫的版本號是1,就只會創建一張Category表。這樣當用戶是直接安裝的第二版的程序時,就會將兩張表一起創建。而當用戶是使用第二版的程序覆蓋安裝第一版的程序時,就會進入到升級數據庫的操作中,此時由于 Book 表已經
存在了,因此只需要創建一張 Category表即可。
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
default:
}
}
//需求2:需要在 Book表中添加一個 category_id 的字段
- 修改原先的建表語句,添加一個 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 中每一個 case 的最后都是沒有使用break的,為什么要這么做呢?這是為了保證在跨版本升級的時候,每一次的數據庫修改都能被全部執行到。
- 使用這種方式來維護數據庫的升級,不管版本怎樣更新,都可以保證數據庫的表結構是最新的,而且表中的數據也完全不會丟失了。