第 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的,为什么要这么做呢?这是为了保证在跨版本升级的时候,每一次的数据库修改都能被全部执行到。
- 使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据也完全不会丢失了。
网友评论