使用
可以使用安卓原生的工具类:
SQLiteOpenHelper 抽象类:通过从此类继承实现用户类,来提供数据库打开、关闭等操作函数。
SQLiteDatabase 数据库访问类:执行对数据库的插入记录、查询记录等操作。
SQLiteCursor 查询结构操作类:用来访问查询结果中的记录。
也可以使用greendao等三方框架,类似于后台开发数据库的操作,赋值给对象,在数据库中对对象进行增删改查操作,
符合java的面向对象的定义,简化流程,效率高。
性能优化
(1)编译SQL语句
Sqlite想要执行操作,需要将程序中的sql语句编译成对应的SQLiteStatement,比如select * from record这一句,被执行100次就需要编译100次。对于批量处理插入或者更新的操作,我们可以使用显示编译来做到重用SQLiteStatement。
想要做到重用SQLiteStatement也比较简单,基本如下:
编译sql语句获得SQLiteStatement对象,参数使用?代替
在循环中对SQLiteStatement对象进行具体数据绑定,bind方法中的index从1开始,不是0
如下向person表中插入100条数据:
public void insertBatchPreCompile() {
long start = SystemClock.currentThreadTimeMillis();
String sql = "insert into " + TAB_PERSON + " values (?,'test','1');";
SQLiteStatement sqLiteStatement = getReadableDatabase().compileStatement(sql);
int count = 0;
while (count < 100) {
count++;
sqLiteStatement.clearBindings();
sqLiteStatement.bindLong(1, count);
sqLiteStatement.executeInsert();
}
Log.e(TAG, "insert recompile use time " + (SystemClock.currentThreadTimeMillis() - start));
}
(2)显示使用事务
在Android中,无论是使用SQLiteDatabase的insert,delete等方法还是execSQL都开启了事务,来确保每一次操作都具有原子性,使得结果要么是操作之后的正确结果,要么是操作之前的结果。
然而事务的实现是依赖于名为rollback journal文件,借助这个临时文件来完成原子操作和回滚功能。既然属于文件,就符合Unix的文件范型(Open-Read/Write- Close),因而对于批量的修改操作会出现反复打开文件读写再关闭的操作。然而好在,我们可以显式使用事务,将批量的数据库更新带来的journal文件打开关闭降低到1次。
具体的实现代码如下:
public void insertWithTransaction() {
long start = SystemClock.currentThreadTimeMillis();
int count = 0;
try {
getWritableDatabase().beginTransaction();
while (count++ < 100) {
insert(count, "test", 1);
}
getWritableDatabase().setTransactionSuccessful();
} catch (Exception e) {
e.printStackTrace();
} finally {
getWritableDatabase().endTransaction();
}
Log.e(TAG, "insert traceaction use time " + (SystemClock.currentThreadTimeMillis() - start));
}
使用这两种方式分别优化,对比效果如下:
这里写图片描述
从图中可以看到在插入100条的情况下,使用预编译的方式可以稍微提升性能,但是使用事务,能够使性能提升大概8倍,所以可以看出频繁的IO操作还是比较耗时的。同时使用两种方式进行优化,可以提升17倍,优化效果非常明显。
(3)建立索引
a.索引的概念
索引,使用索引可快速访问数据库表中的特定信息。索引是对数据库表中一列或多列的值进行排序的一种结构。
在关系数据库中,索引是一种与表有关的数据库结构,它可以使对应于表的SQL语句执行得更快。索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。当表中有大量记录时,若要对表进行查询,第一种搜索信息方式是全表搜索,是将所有记录一一取出,和查询条件进行一一对比,然后返回满足条件的记录,这样做会消耗大量数据库系统时间,并造成大量磁盘I/O操作;第二种就是在表中建立索引,然后在索引中找到符合查询条件的索引值,最后通过保存在索引中的ROWID(相当于页码)快速找到表中对应的记录。
索引是一个单独的、物理的数据库结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。
索引提供指向存储在表的指定列中的数据值的指针,然后根据您指定的排序顺序对这些指针排序。数据库使用索引的方式与您使用书籍中的索引的方式很相似:它搜索索引以找到特定值,然后顺指针找到包含该值的行。
b.建立索引
创建索引的基本语法:
CREATE INDEX index_name ON table_name;
1
创建单列索引
CREATE INDEX index_name ON table_name;
1
c.索引的利弊
毋庸置疑,索引加速了我们检索数据表的速度。然而正如西方谚语 “There are two sides of a coin”,索引亦有缺点:
对于增加,更新和删除来说,使用了索引会变慢,比如你想要删除字
列表内容典中的一个字,那么你同时也需要删除这个字在拼音索引和部首索引中的信息。
建立索引会增加数据库的大小,比如字典中的拼音索引和部首索引实际上是会增加字典的页数,让字典变厚的。
为数据量比较小的表建立索引,往往会事倍功半。
所以使用索引需要考虑实际情况进行利弊权衡,对于查询操作量级较大,业务对要求查询要求较高的,还是推荐使用索引的。
(4)查询数据优化
按需获取列信息
db.query(TableDefine.TABLE_RECORD, null, null, null, null, null, null) ;
1
其中上面方法的第二个参数类型为String[],意思是返回结果参考的colum信息,传递null表明需要获取全部的column数据。如果我们不需要所有列的信息,最好指定一下需要的列。
提前获取索引
例如下面的代码,我们可以把获取列索引的代码cursor.getColumnIndex(TableDefine.COLUMN_INSERT_TIME)放到循环外,这样不需要每次获取。
private void badQueryWithLoop(SQLiteDatabase db) {
Cursor cursor = db.query(TableDefine.TABLE_RECORD, new String[]{TableDefine.COLUMN_INSERT_TIME}, null, null, null, null, null) ;
while (cursor.moveToNext()) {
long insertTime = cursor.getLong(cursor.getColumnIndex(TableDefine.COLUMN_INSERT_TIME));
}
}
(5)ContentValues的容量调整
SQLiteDatabase提供了方便的ContentValues简化了我们处理列名与值的映射,ContentValues内部采用了 HashMap来存储Key-Value数据,ContentValues的初始容量是8,如果当添加的数据超过8之前,则会进行双倍扩容操作,因此建议对ContentValues填入的内容进行估量,设置合理的初始化容量,减少不必要的内部扩容操作。
(6)及时关闭Cursor
(7)耗时异步化
数据库的操作,属于本地IO,通常比较耗时,如果处理不好,很容易导致ANR,因此建议将这些耗时操作放入异步线程中处理。