《第一行代碼》學習筆記 第 6 章

第 6 章 數據存儲全方案,詳解持久化技術

一:文件存儲

  • 將數據存儲到文件中(使用 Java 流的方式將數據寫入到文件中
    1. 這里通過openFileOutput()方法能夠得到一個 FileOutputStream 對象
    2. 然后再借助它構建出一個 OutputStreamWriter 對象
    3. 接著再使用 OutputStreamWriter 構建出一個 BufferedWriter 對象
    4. 這樣你就可以通過 BufferedWriter.write 來將文本內容寫入到文件中了
    5. 關閉文件流
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();
        }
    }
}
  • 從文件中讀取數據
    1. 首先通過 openFileInput()方法獲取到了一個 FileInputStream 對象,
    2. 然后借助它又構建出了一個 InputStreamReader 對象,
    3. 接著再使用 InputStreamReader 構建出一個 BufferedReader 對象,
    4. 這樣我們就可以通過 BufferedReader 進行一行行地讀取,把文件中所有的文本內容全部讀取出來并存放在一個StringBuilder對象中,
    5. 關閉文件流
    6. 最后將讀取到的內容返回就可以了
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 中

    1. 首先需要獲取到SharedPreferences對象(有三種方法可以獲取)
      • Context類中的 getSharedPreferences()方法
      • Activity類中的 getPreferences()方法(這個方法時會自動將當前活動的類名作為 SharedPreferences的文件名)
      • PreferenceManager類中的 getDefaultSharedPreferences()方法(這是一個靜態方法,它接收一個Context參數,并自動使用當前應用程序的包名作為前綴來命名 SharedPreferences文件)
    2. 調用 SharedPreferences對象的 edit()方法來獲取一個 SharedPreferences.Editor 對象。
    3. 向 SharedPreferences.Editor 對象中添加數據,比如添加一個布爾型數據就使用putBoolean 方法
    4. 調用 commit() 或apply() 方法將添加的數據提交,從而完成數據存儲操作。
  • 從 SharedPreferences 中讀取數據

    1. 首先通過 getSharedPreferences()方法得到 SharedPreferences 對象,
    2. 然后分別調用它的 getString()、getInt()和getBoolean()方法去獲取前面所存儲的數據,如果沒有找到相應的值就會使用方法中傳入的默認值來代替

三:SQLite 數據庫存儲

  • 創建數據庫
    getReadableDatabase() 和getWritableDatabase()。這兩個方法都可以創建或打開一個現有的數據庫(如果數據庫已存在則直接打開,否則創建一個新的數據庫) ,并返回一個可對數據庫進行讀寫操作的對象。不同的是,當數據庫不可寫入的時候(如磁盤空間已滿)getReadableDatabase()方法返回的對象將以只讀的方式去打開數據庫,而 getWritableDatabase()方法則將出現異常。
    1. 新建自定義的數據庫類 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,就會進入到設備的控制臺進行數據庫操作)

  • 升級數據庫

    1. 數據庫類中定義新的表格
    public static final String CREATE_CATEGORY = "create table Category ("
        + "id integer primary key autoincrement, "
        + "category_name text, "
        + "category_code integer)";
    
    1. 在數據庫類的onCreate方法中添加新建數據表命令
        db.execSQL(CREATE_CATEGORY);
    
    1. 在數據庫類的onUpgrade方法中添加命令 (刪除已存在的表格,再新建)
        db.execSQL("drop table if exists Book");
        db.execSQL("drop table if exists Category");
        onCreate(db);
    
    1. 在代碼中需要更新數據庫的地方調用以下代碼(記得升級版本號)
        dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
        dbHelper.getWritableDatabase();
    
  • 添加數據(getReadableDatabase()或getWritableDatabase()方法都會返回一個SQLiteDatabase對象,借助這個對象就可以對數據進行添加、刪除、更新等操作

    1. 我們先獲取到了 SQLiteDatabase 對象,
    2. 然后使用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語句實現以上操作)

    1. 添加數據的方法如下:
        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" });
    
    1. 更新數據的方法如下:
        db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99","The Da Vinci Code" });
    
    1. 刪除數據的方法如下:
        db.execSQL("delete from Book where pages > ?", new String[] { "500" });
    
    1. 查詢數據的方法如下:
        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表

  1. 在 onCreate()方法里我們新增了一條建表語句,
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);    //新增了一條建表語句
    }
  1. 然后又在 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 的字段

  1. 修改原先的建表語句,添加一個 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)";
  1. 修改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:
        }
    }

注:

  1. switch 中每一個 case 的最后都是沒有使用break的,為什么要這么做呢?這是為了保證在跨版本升級的時候,每一次的數據庫修改都能被全部執行到。
  2. 使用這種方式來維護數據庫的升級,不管版本怎樣更新,都可以保證數據庫的表結構是最新的,而且表中的數據也完全不會丟失了。
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,517評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,087評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,521評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,493評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,207評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,603評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,624評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,813評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,364評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,110評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,305評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,874評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,532評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,953評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,209評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,033評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,268評論 2 375

推薦閱讀更多精彩內容