持久化技术简介
数据持久化就是指将那些内存中的瞬时数据保存到存储设备中,保证即使在手机或计算机关机的情况下,这些数据仍然不会丢失。保存在内存中的数据是处于瞬时状态的,而保存在存储设备中的数据是处于持久状态的。持久化技术提供了一种机制,可以让数据在瞬时状态和持久状态之间进行转换。持久化技术被广泛应用于各种程序设计领域,而本节要探讨的自然是Android中的数据持久化技术。
文件存储
文件存储是Android中最基本的数据存储方式,它不对存储的内容进行任何格式化处理,所有数据都是原封不动地保存到文件当中的,因而它比较适合存储一些简单的文本数据或二进制数据。如果你想使用文件存储的方式来保存一些较为复杂的结构化数据,就需要定义一套自己的格式规范,方便之后将数据从文件中重新解析出来。
那么首先我们就来看一看,Android中是如何通过文件来保存数据的。
将数据存储到文件中
Context类中提供了一个openFileOutput()方法,可以用于将数据存储到指定的文件中。
这个方法接收两个参数:
第一个参数是文件名,在文件创建的时候使用,注意这里指定的文件名不可以包含路径,因为所有的文件都默认存储到/data/data//files/目录下;
第二个参数是文件的操作模式,主要有MODE_PRIVATE和MODE_APPEND两种模式可选,默认是MODE_PRIVATE,表示当指定相同文件名的时候,所写入的内容将会覆盖原文件中的内容,而MODE_APPEND则表示如果该文件已存在,就往文件里面追加内容,不存在就创建新文件。
其实文件的操作模式本来还有另外两种:MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE。
这两种模式表示允许其他应用程序对我们程序中的文件进行读写操作,不过由于这两种模式过于危险,很容易引起应用的安全漏洞,已在Android 4.2版本中被废弃。
openFileOutput()方法返回的是一个FileOutputStream对象,得到这个对象之后就可以使用Java流的方式将数据写入文件中了。
以下是一段简单的代码示例,展示了如何将一段文本内容保存到文件中:
public void save() { String data = "Data to save"; FileOutputStream out = null; BufferedWriter writer = null; try { // 使用 openFileOutput 创建 FileOutputStream 对象 out = openFileOutput("data", Context.MODE_PRIVATE); // 使用 OutputStreamWriter 包装 FileOutputStream,然后使用 BufferedWriter 包装以提供高效写入 writer = new BufferedWriter(new OutputStreamWriter(out)); // 将数据写入文件 writer.write(data); } catch (IOException e) { // 打印异常信息 e.printStackTrace(); } finally { try { // 关闭 BufferedWriter if (writer != null) { writer.close(); } } catch (IOException e) { // 打印异常信息 e.printStackTrace(); } } }
如果你已经比较熟悉J悉Java流了,上面的代码一定不难理解吧。这里通过openFileOutput()方法能够得到一个FileOutputStream对象,然后借助它构建出一个OutputStreamWriter对象,接着再使用OutputStreamWriter构建出一个BufferedWriter对象,这样你就可以通过BufferedWriter将文本内容写入文件中了。
下面我们就编写一个完整的例子,借此学习一下如何在Android项目中使用文件存储的技术。首先创建一个FilePersistenceTest项目,并修改activity_main.xml中的代码,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Type something here" /> </LinearLayout>
这里只是在布局中加入了一个EditText,用于输入文本内容。其实现在你就可以运行一下程序了,界面上肯定会有一个文本输入框。然后在文本输入框中随意输入点什么内容,再按下Back键,这时输入的内容肯定就已经丢失了,因为它只是瞬时数据,在Activity被销毁后就会被回收。而这里我们要做的,就是在数据被回收之前,将它存储到文件当中。
修改MainActivity中的代码,
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化 EditText 组件,这里假设在 onCreate 中进行初始化 EditText editText = (EditText) findViewById(R.id.edit_text_id); } @Override protected void onDestroy() { super.onDestroy(); EditText editText = (EditText) findViewById(R.id.edit_text_id); // 再次获取 EditText,确保在 onDestroy 中也能获取到 save(editText.getText().toString()); } private void save(String inputText) { try { OutputStream output = openFileOutput("data", Context.MODE_PRIVATE); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output)); writer.write(inputText); writer.close(); // 确保在写入后关闭 writer } catch (IOException e) { e.printStackTrace(); } } }
//此处照着书上代码报错,ai代码没法实现,不知道怎么回事
可以看到,首先我们重写了onDestroy()方法,这样就可以保证在Activity销毁之前一定会调用这个方法。在onDestroy()方法中,我们获取了EditText中输入的内容,并调用save()方法把输入的内容存储到文件中,文件命名为data。save()方法中的代码和之前的示例基本相同,这里就不再做解释了。
从文件中读取数据
Context类中还提供了一个openFileInput()方法,用于从文件中读取数据。这个方法要比openFileOutput()简单一些,它只接收一个参数,即要读取的文件名,然后系统会自动到/data/data//files/目录下加载这个文件,并返回一个FileInputStream对象,得到这个对象之后,再通过流的方式就可以将数据读取出来了。
数据库
定义合约类
什么是合约类
接下来创建数据库管理类
读写占用资源不一样
读少写多
调用该函数也占资源,所以一般只保存一个该对象
1 创建数据库
Android为了方便地管理数据库,专门提供了一个SQLiteOpenHelper帮助类,借助这个类,可以很简单创建和升级数据库。SQLiteOpenHelper是一个抽象类。 所以需要我们创建一个自己的帮助类去继承它。
SQLiteOpenHelper中有两个抽象方法分别是onCreate和onUpgrade,我们必须在自己的帮助类中重写这两个方法。然后分别在这两个方法中去实现创建和升级数据库的逻辑。
SQLiteOpenHelper中有两个非常重要的实例方法getReadableDatabase和getWriteableDatabase这两个方法可以创建或者打开一个现有的数据库。 (存在则打开,否则则创建)并返回一个对数据库进行读写操作的对象。
不过如果数据库不可以写入的时候 (比如磁盘满了) ,这个时候getReadableDatabase方法返回对象将以只读的方式打开数据库,而getWritableDatabase则出现异常。
SQLiteOpenHelper有两个构造方法提供重写。不过我们一般使用参数少一点的构造方法即可。
这个构造方法接收4个参数。
- 第一个是Context
- 第二个是数据库名。
- 第三个是允许我们查询数据时候返回一个自定义的Cursor,一般为null
- 第四个是当前数据库的版本号。可用于对数据库进行升级操作,
构建出SQLiteOpenHelper实例后,再调用getReadableDatqbase或者getWritableDatabase方法就能够创建数据库了。
数据库文件存储在data/data /databases/目录下面
创建一个新项目,更改其中activityxml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/main" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <Button android:id="@+id/createdb_btn" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="创建数据库" /> </LinearLayout>
MainActivity:
package com.example.hellowworld; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import androidx.activity.EdgeToEdge; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); Button button = findViewById(R.id.createdb_btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } }); } }
新创建java类
package com.example.hellowworld; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.widget.Toast; import androidx.annotation.Nullable; public class Mydatahelper extends SQLiteOpenHelper { 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 Mydatahelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); mContext = context; } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_BOOK); Toast.makeText(mContext,"创建成功啊啊啊啊啊",Toast.LENGTH_LONG).show(); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { } }
SQL建立表语句:
create table Book( id integer primary key autoincrement, author text, price real, pages integer, name text)
integer
表示整型,real
表示浮点型,txt
表示文本类型,blob
表示二进制类型,primary key
设置主键,autoincrement
显示id是自动增长的。
public SQLiteOpenHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version)
构造方法的含义:
context:Android 应用程序的上下文
name: 这是要创建或打开的数据库的名称。如果数据库不存在SQLiteOpenHelper 会创建一个新的数据库文件,如果数据库已经存在,它将尝试打开该数据库。这里传入的数据库名称可以是一个字符串,表示数据库的名称。
factory:这是一个用于创建游标对象的工厂。游标是用于查询数据库并遍历结果集的对象。通常情况下,你可以传入 null。
version:这是数据库的版本号。当应用程序需要进行数据库结构的更改时,你需要增加数据库版本号。
更新版本
我们的Helper中 还有一个onUpgrade方法,用于对数据库进行升如果我们想在往数据库books.db中添加一张表Category用于记录书籍的分类。那要怎么做呢?建表语句
create table Category( id integer primary key autoincrement, category_name text category_code integer)
新建表存入字符串,之后在oncreate中加入新的db.execSQL(CREATE_BOOK_TITLE);
在onUpgrade中加入删除数据库的语句,再调用oncreate
package com.example.hellowworld; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.widget.Toast; import androidx.annotation.Nullable; public class Mydatahelper 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_BOOK_TITLE = "create table BOOK_TITLE(" + "id integer primary key autoincrement," + "category_name text," + "category_code integer)"; private Context mContext; public Mydatahelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); mContext = context; } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_BOOK); db.execSQL(CREATE_BOOK_TITLE); Toast.makeText(mContext,"创建成功啊啊啊啊啊",Toast.LENGTH_LONG).show(); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("drop table if exists Book"); db.execSQL("drop table if exists BOOK_TITLE"); Toast.makeText(mContext,"升级成功啊啊啊啊啊",Toast.LENGTH_LONG).show(); onCreate(db); } }
增删改查
我们对数据库的操作无非就是四种增删改查 (CRUD)每一种都有对应的SQL命令。
增加用inset
查询用select
删 除用delete
更新用update
不过android提供了一系列的辅助方法,使得开发人员不用sql就可以轻松完成CRUD操作
SQLiteOpenHelper类提供两个方法
getReadableDatabase和getWritableDatabase 用来进行创建和升级数据库。
这两个方法都会返回一个SQLiteDatabase对象,借助这个对象 我们可以对数据进行CRUD
增加:
我们添加一个按钮
并为按钮注册监听,
SQLiteDatabase sql = dp.getWritableDatabase();
ContentValues contentValues = new ContentValues();
先获取一下getWritableDatabase对象,用于插入操作
再声明一个ContentValues,用于存键值对
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); Mydatahelper dp = new Mydatahelper(this,"firsttry",null,3); Button button = findViewById(R.id.createdb_btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dp.getWritableDatabase(); } }); Button button1 = findViewById(R.id.newdb_btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase sql = dp.getWritableDatabase(); ContentValues contentValues = new ContentValues(); //增加 contentValues.put("author", "dyp"); contentValues.put("name", "ytbhhhh"); contentValues.put("pages", 567); contentValues.put("price", 96.5); //调用sqlite插入记录 sql.insert("Book",null,contentValues); //继续增加需要情况contentvalues contentValues.clear(); contentValues.put("author", "dyp1"); contentValues.put("name", "xixiytbhhhh"); contentValues.put("pages", 23323); contentValues.put("price", 32.6); //调用sqlite插入记录 sql.insert("Book",null,contentValues); } }); } }
更新内容:
在android中更新数据也是非常方便的。下面我们添加一个按钮 把书本的名称改为英文。
sqLiteDatabase对象提供update方法。
它需要四个参数
- 前面两个参数和insert方法一样。
- 第三个参数 是sql的where部分 使用? 来表示占位符
- 然后第四个参数 是一个字符串数组 每一个元素来替换第三个字符串中的? 号 按顺序来的
下面我们来演示一下 把书本的名称改为英文
public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); setContentView(R.layout.activity_main); ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom); return insets; }); Mydatahelper dp = new Mydatahelper(this,"firsttry",null,3); Button button = findViewById(R.id.createdb_btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dp.getWritableDatabase(); } }); Button button1 = findViewById(R.id.newdb_btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase sql = dp.getWritableDatabase(); ContentValues contentValues = new ContentValues(); //增加 contentValues.put("author", "dyp"); contentValues.put("name", "ytbhhhh"); contentValues.put("pages", 567); contentValues.put("price", 96.5); //调用sqlite插入记录 sql.insert("Book",null,contentValues); //继续增加需要情况contentvalues contentValues.clear(); contentValues.put("author", "dyp1"); contentValues.put("name", "xixiytbhhhh"); contentValues.put("pages", 23323); contentValues.put("price", 32.6); //调用sqlite插入记录 sql.insert("Book",null,contentValues); } }); Button button2 = findViewById(R.id.refreshdb_btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase sql = dp.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put("name","wanmei"); sql.update("Book",null,"id = ?",new String[]{"1"}); } }); } }
删除数据
删除数据是使用sqLiteDatabase对象提供delete方法。
它是专门用来删除数据的。它接收三个参数。
第一个是表名第二个也是where条件第三个是字符串数组 用来替换第二个中的占位符。
下面我们来试试 把第id为1的记录删除。
Button button3 = findViewById(R.id.delatedb_btn); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase sql = dp.getWritableDatabase(); sql.delete("Book","id = ?",new String[]{"1"}); } });
查找
sqLiteDatabase提供query方法来进行查询。它最短的方法需要传入7个参数。
获取一个Cursor类对象
Cursor类
Cursor类似于jDBC中的ResultSet类用于记录查询结果, 提供如下方法: (1) move(int offset);将记录指针向上或向下移动指定行数, offset为负表示向上移动, offset为正数表示向下移动 (2) boolean moveToFirst();将记录指针移动到第一行 (3) boolean moveToLast();将记录指针移动到最后一行 (4) boolean moveToNext();将记录指针移动到下一条记录 (5) boolean moveToPosition(int position);将记录指针移动到指定行 (6) boolean moveToPrevious();将记录指针移动到上一行. (7) getXXX(); 获取一行记录中指定列的数据 (8)getCount() 总记录条数 (9)isFirst() 判断是否第一条记录 (10)isLast()判断是否最后一条记录
之后我们操作Cursor类对象,一般用while循环+movetonext函数遍历,并在遍历之中查找
public static void queryData() { Connection conn = null; Statement stmt = null; try { conn = DriverManager.getConnection("jdbc:sqlite:mydatabase.db"); stmt = conn.createStatement(); String sql = "SELECT * FROM mytable"; ResultSet rs = stmt.executeQuery(sql); while(rs.next()) { // 通过列名获取数据 String column1 = rs.getString("column1"); String column2 = rs.getString("column2"); System.out.println("Column1 = " + column1 + ", Column2 = " + column2); } } catch (SQLException e) { System.err.println(e.getMessage()); } finally { try { if (stmt != null) { stmt.close(); } if (conn != null) { conn.close(); } } catch (SQLException e) { System.err.println(e.getMessage()); } } }