简介
数据持久化就是将那些在内存中的瞬时数据保存到存储设备中,保证计时在设备掉电的情况下,这些数据仍然不会丢失。保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据是处于持久状态的,持久化技术则是提供了一种机制可以让数据在瞬时状态和持久化状态之间进行转换。
1. 文件存储
文件存储是Android中最基本的一种数据存储方式,它不对存储的内容进行任何的格式化处理,所有数据都是原封不动的保存到文件当中,因而它比较适合用于存储一些简单的文本数据或二进制数据。
1.1 将数据存储到文件中
Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中,这个方法接收两个参数,第一个参数是文件名,在文件创建的时候使用就是这个名称,注意这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到/data/data/< package name >/files/目录下。第二个参数是文件的操作模式,主要有两种模式可选MODE_PRIVATE和MODE_APPEND。其中MODE_PRIVATE是默认的操作模式,表示当指定同样文件名的时候,所写入的内容将会覆盖原文件中的内容,而MODE_APPEND则表示如果该文件已存在就往文件里面追加内容,不存在就创建新文件,其实文件的操作模式本来还有另外两种,MODE_WORLD_READABLE和MODE_WORLD_WRITABLE,这两种模式表示允许其他的应用程序对我们程序中的文件进行读写操作,不过由于这两种模式过于危险,很容易引起应用的安全性漏洞,已经在Android 4.2版本中被废弃。
openFileOutput()方法返回的是一个FileOutputStream对象,得到这个对象之后就就可以使用Java流的方式将数据写入到文件中。以下是一段简单的代码示例,展示了如何将一段文本内容保存到文件中:
public void sava() {
String data = "Data to sava";
FileOutputStream out = null;
BufferedWriter writer = null;
try {
out = openFileOutput("data", Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(data);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
1.2 从文件中读取数据
public String load() {
String result = null;
FileInputStream in = null;
BufferedReader reader = null;
StringBuilder builder = new StringBuilder();
try {
in = openFileInput("data");
reader = new BufferedReader(new InputStreamReader(in));
while((result = reader.readLine()) != null) {
builder.append(result);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return builder.toString();
}
注意:文件存储的方式并不适合用于保存一些较为复杂的文本数据。
2. SharedPreference
SharedPreference是使用键值对的方式来存储数据的,并且SharedPreference还支持多种不同的数据类型存储,如果存储的数据类型是整形,那么读取出来的数据也是整形,存储的数据是一个字符串,读取出来的数据仍然是字符串。
2.1 将数据存储到SharedPreference中
要想使用SharedPreference存储数据,首先需要获取到SharedPreference对象。Android中主要提供了三种方法用于得到SharedPreference对象。
- Context类中的getSharedPreference()方法
此方法接收两个参数,第一个参数用于指定SharedPreference文件的名称,如果指定的文件不存在则会创建一个,SharedPreference文件都是存放在/data/data//shared_prefs/目录下。第二个参数用于指定操作模式,主要有两种模式可以选择,MODE_PRIVATE和MODE_MULTI_PROCESS。MODE_PRIVATE是默认的操作模式,数值时0,表示只有当前的应用程序才可以对这个SharedPreference文件进行读写。MODE_MULTI_PROCESS则一般式用于多个进程中对同一个SharedPreference文件进行读写的情况。
- Activity类的getPreferences()方法
与上述方法类似,不过只接受一个操作模式参数,使用这个方法会自动将当前活动的类名作为SharedPreference的文件名。
- PreferenceManager类中的getDefaultSharedPreferences()方法
这是一个静态方法,它接收一个Context参数,并自动使用当前应用程序的包名作为前缀来命名SharedPreference文件。
得到了SharedPreference对象之后,就可以开始向SharedPreference文件中存储数据了,主要可以分为为三步实现。
SharedPreferences sp = getSharedPreferences("data", Context.MODE_PRIVATE);
Editor editor = sp.edit(); //1.获取Editor对象
editor.putBoolean("isfinished", true); //2.添加数据
editor.putString("nam", "liu");
editor.commit(); //3.提交数据
editor.clear() //将文件SharedPreference中的数据全部清除掉
2.1 从SharedPreference中读取数据
SharedPreferences sp = getSharedPreferences("data", Context.MODE_PRIVATE); //获取SharedPreference对象
boolean isFinished = sp.getBoolean("isfinished", false);
String name = sp.getString("name", "");
3. SQLite
3.1 创建数据库
SQLiteOpenHelper是一个抽象类,我们使用它的话需要创建一个新的类继承它。并重写SQLiteOpenHelper中的两个抽象方法,分别是onCreate()和onUpgrade(),然后分别在两个方法中实现创建、升级数据库的逻辑。数据库创建后存放在/data/data/databases/目录下
public class MyDatabaseHelper extends SQLiteOpenHelper {
//创建一个名为Book的表
/**
* integer表示整数 primary key表示主键 autoincrement关键字表示id列是自增长的
* real表示浮点型
* text表示文本类型 blob表示二进制类型
*//
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
private Context mContext;
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) {
}
}
创建数据库
public class MainActivity extends Activity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1); 版本号为1
Button createDatabase = (Button) findViewById(R.id.create_database);
createDatabase.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dbHelper.getWritableDatabase();
}
});
}
}
如上所述,当第一次点击createDatabase按钮时,就会检测到当前程序中并没有BookStore.db数据库,于是会创建该数据库并调用MyDatabaseHelper中的onCreate方法,这Book表也创建了,再次点击此按钮时,不会再创建一次。
3.2 升级数据库
在数据库中有一张Book表的情况下,想再添加一张Category的表用于记录书籍的分类该如何做呢?
3.2.1 onUpgrade()
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
private Context mContext;
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);
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists Book");
db.execSQL("drop table if exists Category");
onCreate(db);
}
}
3.2.2 修改MyDatabaseHelper构造函数的版本好参数
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
3.3 添加数据
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);
3.4 更新数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
db.update("Book", values, "name = ?",
new String[] { "The Da Vinci Code" });
3.5 删除数据
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });
3.6 查询数据
query()方法参数 | 对应SQL部分 | 描述 |
---|---|---|
table | from table_name | 指定查询的表名 |
colums | select column1,column2 | 指定查询的列名 |
selection | where column=value | 指定where的约束条件 |
selectionArgs | 为where中的占位符提供具体的值 | |
groupBy | group by column | 指定需要group by的列 |
having | having volumn=value | 对group by的结果进一步约束 |
orderBy | order by column1 | 指定查询结果后的排序方式 |
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor cursor = db.query("Book", null, null, null, null, null,
null);
if (cursor.moveToFirst()) {
do {
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();
3.7 使用SQL语句操作数据库
增
insert into Book(name,author) values('中国国家地理','国家地理')
删
delete from Book where name = '中国国家地理'
改
update Book set author='liuguoquan' where name='中国国家地理'
查
select * from Book where name='中国国家地理'
4. SQLite数据库的最佳实践
4.1 使用事务
SQLite数据库是支持事务的,事务的特性可以保证让某一系列的操作要么全部完成,要么全部不会完成。
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction(); //开启事务
try {
db.delete("Book", null, null);
//if (true) {
// 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(); //结束事务
}
4.2 升级数据库的最佳写法
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, " + "author text, "
+ "price real, " + "pages integer, " + "name text)";
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, " + "category_name text, "
+ "category_code integer)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
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的,为什么要这么做呢》这是为了保证在跨版本升级的是时候,每一次的数据库修改都能被全部执行到。比如用户当前是从第二版程序升级到第三版,那么case2中的逻辑就会执行。而如果用户当前是从第一版程序升级到第三版程序的,那么case1和case2中的逻辑都会执行。使用这种方式来维护数据库的升级,不管版本怎么更新,都可以保证数据库的表结构是最新的,而且表中的数据也完全不会丢失了。