Android数据库一些源码分析
对于批量数据插入这种最常见的情况来说,我们来看两种实现方式(两种都用了事务)。
下面这种应该是最多人使用的插入数据的方法:
public long addByExec(List<Person> persons) { long start = System.currentTimeMillis();
db.beginTransaction(); for (Person person : persons) {
db.execSQL(" INSERT INTO person(name,age,info) VALUES(?, ?, ?) ",
new Object[] { person.name, person.age, person.info });
} db.setTransactionSuccessful();
long end = System.currentTimeMillis();
db.endTransaction();
return end - start; }
再看一种比较少用的插入方法
public long addByStatement(List<Person> persons) {
long start = System.currentTimeMillis();
db.beginTransaction();
SQLiteStatement sqLiteStatement = db.compileStatement(sql); for (Person person : persons) {
sqLiteStatement.bindString(1, person.name);
sqLiteStatement.bindString(2, person.age);
sqLiteStatement.bindString(3, person.info);
sqLiteStatement.executeInsert();
}
db.setTransactionSuccessful();
long end = System.currentTimeMillis();
db.endTransaction();
return end - start;
}
然后我们分别用这两个方法 来向数据库里面插入一万条数据 看看耗时多少。为了演示效果更加突出一点,我录制了一个GIF,同时,
这2个方法我也没有用子线程来操作他,直接在ui线程上操作 所以看起来效果会比较突出一些(但是自己写代码的时候千万别这么写小心ANR)。
可以看出来后者耗时几乎只有前者的 一半(所以以后大家在做大批量数据插入的时候可以考虑后者的实现方式)。我们来看看源代码为啥会这样。
首先看前者的实现方法源码
public void execSQL(String sql, Object[] bindArgs) throws SQLException {
if (bindArgs == null) {
throw new IllegalArgumentException("Empty bindArgs");
}
executeSql(sql, bindArgs);
} private int executeSql(String sql, Object[] bindArgs) throws SQLException {
if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
disableWriteAheadLogging();
mHasAttachedDbs = true;
}
SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
try {
return statement.executeUpdateDelete();
} catch (SQLiteDatabaseCorruptException e) {
onCorruption();
throw e;
} finally {
statement.close();
}
}
我们发现 前者的实现 实际上最后也是通过SQLiteStatement 这个类是操作的。
而后者不过是
public SQLiteStatement compileStatement(String sql) throws SQLException {
verifyDbIsOpen();
return new SQLiteStatement(this, sql, null);
}
所以实际上前者之所以比后者耗时 应该是下面这段代码的原因:
if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
disableWriteAheadLogging();
mHasAttachedDbs = true;
}
public static int getSqlStatementType(String sql) {
sql = sql.trim();
if (sql.length() < 3) {
return STATEMENT_OTHER;
}
String prefixSql = sql.substring(0, 3).toUpperCase();
if (prefixSql.equals("SEL")) {
return STATEMENT_SELECT;
} else if (prefixSql.equals("INS") ||
prefixSql.equals("UPD") ||
prefixSql.equals("REP") ||
prefixSql.equals("DEL")) {
return STATEMENT_UPDATE;
} else if (prefixSql.equals("ATT")) {
return STATEMENT_ATTACH;
} else if (prefixSql.equals("COM")) {
return STATEMENT_COMMIT;
} else if (prefixSql.equals("END")) {
return STATEMENT_COMMIT;
} else if (prefixSql.equals("ROL")) {
return STATEMENT_ABORT;
} else if (prefixSql.equals("BEG")) {
return STATEMENT_BEGIN;
} else if (prefixSql.equals("PRA")) {
return STATEMENT_PRAGMA;
} else if (prefixSql.equals("CRE") || prefixSql.equals("DRO") ||
prefixSql.equals("ALT")) {
return STATEMENT_DDL;
} else if (prefixSql.equals("ANA") || prefixSql.equals("DET")) {
return STATEMENT_UNPREPARED;
}
return STATEMENT_OTHER;
}
实际上就是多了一个字符串处理的函数。这就是为什么前者耗时要比后者多。因为实际上直接调用executeSql的时候
他里面是先做字符串处理然后再调用SQLiteStatement来执行,这个过程当然是比我们直接调用SQLiteStatement
来执行速度慢的。
我们首先来看一下下面这个函数
public Cursor queryTest1() {
long start1 = System.currentTimeMillis();
Cursor c = db.rawQuery("select * from t1,t3 where t1.num>t3.num", null);
long end1 = System.currentTimeMillis();
Log.v("DBManager", "time1 need " + (end1 - start1));
long start2 = System.currentTimeMillis();
c.moveToNext();
long end2 = System.currentTimeMillis();
Log.v("DBManager", "time2 need" + (end2 - start2));
long start3 = System.currentTimeMillis();
c.moveToNext();
long end3 = System.currentTimeMillis();
Log.v("DBManager", "time3 need" + (end3 - start3));
return c;
}
一个很常见的,多表查询的函数,有些人可能会奇怪为啥在这个地方我要加那么多日志。实际上如果你t1和t3的数据都很多的话,这个查询是可以预料到的会非常耗时。
很多人都会以为这个耗时是在下面这条语句做的:
Cursor c = db.rawQuery("select * from t1,t3 where t1.num>t3.num", null);
但是实际上这个查询耗时是在你第一调用
c.moveToNext();
来做的,有兴趣的同学可以自己试一下,我们这里就不帮大家来演示这个效果了,但是可以帮助大家分析一下源代码为什么会是这样奇怪的结果?
我们首先来分析一下rawQuery 这个函数
public Cursor rawQuery(String sql, String[] selectionArgs) {
return rawQueryWithFactory(null, sql, selectionArgs, null);
} /**
* Runs the provided SQL and returns a cursor over the result set.
*
* @param cursorFactory the cursor factory to use, or null for the default factory
* @param sql the SQL query. The SQL string must not be ; terminated
* @param selectionArgs You may include ?s in where clause in the query,
* which will be replaced by the values from selectionArgs. The
* values will be bound as Strings.
* @param editTable the name of the first table, which is editable
* @return A {@link Cursor} object, which is positioned before the first entry. Note that
* {@link Cursor}s are not synchronized, see the documentation for more details.
*/
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable) {
verifyDbIsOpen();
BlockGuard.getThreadPolicy().onReadFromDisk(); SQLiteDatabase db = getDbConnection(sql);
SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(db, sql, editTable); Cursor cursor = null;
try {
cursor = driver.query(
cursorFactory != null ? cursorFactory : mFactory,
selectionArgs);
} finally {
releaseDbConnection(db);
}
return cursor;
}
看一下24行,发现是构造了一个driver对象 然后调用这个driver对象的query方法
我们继续跟踪源代码
public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable,
CancellationSignal cancellationSignal) {
mDatabase = db;
mEditTable = editTable;
mSql = sql;
mCancellationSignal = cancellationSignal;
} public Cursor query(CursorFactory factory, String[] selectionArgs) {
final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal);
final Cursor cursor;
try {
query.bindAllArgsAsStrings(selectionArgs); if (factory == null) {
cursor = new SQLiteCursor(this, mEditTable, query);
} else {
cursor = factory.newCursor(mDatabase, this, mEditTable, query);
}
} catch (RuntimeException ex) {
query.close();
throw ex;
} mQuery = query;
return cursor;
}
发现这个返回的cursor实际上就是直接new出来的一个对象
public SQLiteCursor(SQLiteCursorDriver driver, String editTable, SQLiteQuery query) {
if (query == null) {
throw new IllegalArgumentException("query object cannot be null");
}
if (query.mDatabase == null) {
throw new IllegalArgumentException("query.mDatabase cannot be null");
}
mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
mDriver = driver;
mEditTable = editTable;
mColumnNameMap = null;
mQuery = query; query.mDatabase.lock(query.mSql);
try {
// Setup the list of columns
int columnCount = mQuery.columnCountLocked();
mColumns = new String[columnCount]; // Read in all column names
for (int i = 0; i < columnCount; i++) {
String columnName = mQuery.columnNameLocked(i);
mColumns[i] = columnName;
if (false) {
Log.v("DatabaseWindow", "mColumns[" + i + "] is "
+ mColumns[i]);
} // Make note of the row ID column index for quick access to it
if ("_id".equals(columnName)) {
mRowIdColumnIndex = i;
}
}
} finally {
query.mDatabase.unlock();
}
}
所以看到这里我们就能确定的是rawquery这个方法 返回的cursor实际上就是一个对象,并没有任何真正调用sql的地方。
然后我们来看看我们怀疑的moveToNext这个方法因为从日志上看耗时的地方在第一次调用他的时候,所以我们怀疑真正调用查询sql的地方
在这个函数里面被触发。
public final boolean moveToNext() {
return moveToPosition(mPos + 1);
}
public final boolean moveToPosition(int position) {
// Make sure position isn't past the end of the cursor
final int count = getCount();
if (position >= count) {
mPos = count;
return false;
} // Make sure position isn't before the beginning of the cursor
if (position < 0) {
mPos = -1;
return false;
} // Check for no-op moves, and skip the rest of the work for them
if (position == mPos) {
return true;
} boolean result = onMove(mPos, position);
if (result == false) {
mPos = -1;
} else {
mPos = position;
if (mRowIdColumnIndex != -1) {
mCurrentRowID = Long.valueOf(getLong(mRowIdColumnIndex));
}
} return result;
}
看一下那个getcount方法
@Override
public int getCount() {
if (mCount == NO_COUNT) {
fillWindow(0);
}
return mCount;
} private void fillWindow(int startPos) {
clearOrCreateLocalWindow(getDatabase().getPath());
mWindow.setStartPosition(startPos);
int count = getQuery().fillWindow(mWindow);
if (startPos == 0) { // fillWindow returns count(*) only for startPos = 0
if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "received count(*) from native_fill_window: " + count);
}
mCount = count;
} else if (mCount <= 0) {
throw new IllegalStateException("Row count should never be zero or negative "
+ "when the start position is non-zero");
}
}
发现如果满足某个条件的话 就调用fillwindow这个方法,我们来看看是什么条件
/** The number of rows in the cursor */
private volatile int mCount = NO_COUNT;
static final int NO_COUNT = -1;
看到这就明白了,如果你默认的mCount为-1的话就代表你这个cursor里面还没有查过吗,所以必须要调用fillwindow方法
private synchronized SQLiteQuery getQuery() {
return mQuery;
}
我们来看看这个query是什么
/** The query object for the cursor */
private SQLiteQuery mQuery;
看看他的fillwindow方法
/**
* Reads rows into a buffer. This method acquires the database lock.
*
* @param window The window to fill into
* @return number of total rows in the query
*/
/* package */ int fillWindow(CursorWindow window) {
mDatabase.lock(mSql);
long timeStart = SystemClock.uptimeMillis();
try {
acquireReference();
try {
window.acquireReference();
int startPos = window.getStartPosition();
int numRows = nativeFillWindow(nHandle, nStatement, window.mWindowPtr,
startPos, mOffsetIndex);
if (SQLiteDebug.DEBUG_LOG_SLOW_QUERIES) {
long elapsed = SystemClock.uptimeMillis() - timeStart;
if (SQLiteDebug.shouldLogSlowQuery(elapsed)) {
Log.d(TAG, "fillWindow took " + elapsed
+ " ms: window=\"" + window
+ "\", startPos=" + startPos
+ ", offset=" + mOffsetIndex
+ ", filledRows=" + window.getNumRows()
+ ", countedRows=" + numRows
+ ", query=\"" + mSql + "\""
+ ", args=[" + (mBindArgs != null ?
TextUtils.join(", ", mBindArgs.values()) : "")
+ "]");
}
}
mDatabase.logTimeStat(mSql, timeStart);
return numRows;
} catch (IllegalStateException e){
// simply ignore it
return 0;
} catch (SQLiteDatabaseCorruptException e) {
mDatabase.onCorruption();
throw e;
} catch (SQLiteException e) {
Log.e(TAG, "exception: " + e.getMessage() + "; query: " + mSql);
throw e;
} finally {
window.releaseReference();
}
} finally {
releaseReference();
mDatabase.unlock();
}
}
一目了然,其实就是rawquery返回的是一个没有意义的cursor对象里面什么都没有,当你调用movetonext之类的方法的时候,
会判断是否里面没有数据 如果有数据就返回你要的数据,如果没有的话,实际上最终调用的就是SQLiteQuery这个类的fillwindow方法
来最终执行你写的sql语句~~耗时也就是在这里耗时!!!!!切记!不是在rawquery里耗时的!
Android数据库一些源码分析的更多相关文章
- Android网络框架源码分析一---Volley
转载自 http://www.jianshu.com/p/9e17727f31a1?utm_campaign=maleskine&utm_content=note&utm_medium ...
- OpenGL—Android 开机动画源码分析一
.1 Android开机动画实现方式目前实现Android开机动画的方式主要是逐帧动画和OpenGL动画. ?逐帧动画 逐帧动画是一种常见的动画形式(Frame By Frame),其原理是在“连续的 ...
- Android分包MultiDex源码分析
转载请标明出处:http://blog.csdn.net/shensky711/article/details/52845661 本文出自: [HansChen的博客] 概述 Android开发者应该 ...
- Android 数据库 ObjectBox 源码解析
一.ObjectBox 是什么? greenrobot 团队(现有 EventBus.greenDAO 等开源产品)推出的又一数据库开源产品,主打移动设备.支持跨平台,最大的优点是速度快.操作简洁,目 ...
- Android消息机制源码分析
本篇主要介绍Android中的消息机制,即Looper.Handler是如何协同工作的: Looper:主要用来管理当前线程的消息队列,每个线程只能有一个Looper Handler:用来将消息(Me ...
- Android 开机动画源码分析
Android系统在启动SystemServer进程时,通过两个阶段来启动系统所有服务,在第一阶段启动本地服务,如SurfaceFlinger,SensorService等,在第二阶段则启动一系列的J ...
- Android开源框架源码分析:Okhttp
一 请求与响应流程 1.1 请求的封装 1.2 请求的发送 1.3 请求的调度 二 拦截器 2.1 RetryAndFollowUpInterceptor 2.2 BridgeInterceptor ...
- android hardware.c 源码分析
android的jni通过ID来找hal模块,使用了hw_get_module()函数,本文就通过这个函数的来分析一下hal层的模块是如何匹配的. 首先要了解三个结构体hw_module_t,hw_m ...
- Android -- 消息处理机制源码分析(Looper,Handler,Message)
android的消息处理有三个核心类:Looper,Handler和Message.其实还有一个Message Queue(消息队列),但是MQ被封装到Looper里面了,我们不会直接与MQ打交道,因 ...
随机推荐
- hdu5593/ZYB's Tree 树形dp
ZYB's Tree Memory Limit: 131072/131072 K (Java/Others) 问题描述 ZYBZYB有一颗NN个节点的树,现在他希望你对于每一个点,求出离每个点距 ...
- TCP网络拥塞控制
拥塞控制过程 数据吞吐量 TCP窗口大小,窗口流量控制,慢启动对TCP的成块数据传输综合作用,可能对TCP的数据传输有意想不到的影响. RTT(Round-Trip Time) :往返时间.是指一个报 ...
- Mita和Maui
参考:http://blog.csdn.net/popeer/article/details/6002541 UI自动化的框架,MS内部使用的不对外开放的框架.UI Automation 离不开像Mi ...
- java多线程理解2
1. 什么时候必须同步?什么叫同步?如何同步? 要跨线程维护正确的可见性,只要在几个线程之间共享非 final 变量,就必须使用 synchronized(或 volatile)以确保一个线程可以看见 ...
- SinoSure
Sino,就是“中国.东方”的意思, 这个词只能作为前缀使用,不能单独讲.西方社会有时使用“Sino-”来表示“中国(的)”的意思,但是“Sino”均为连接词,并非单独用来表示“中国”之语.如表达中美 ...
- 超级内存NVDIMM:下一代数据中心存储关键技术
1.背景介绍 连接到互联网的设备数量不断增长,到2015年,将达到150亿之多.而数据中心的压力也随之增加,唯有采用新的技术才能进一步提升其效率和性能. 相比于HDD传统硬盘,固态硬盘大大增加了I/O ...
- QStandardItemModel简单好用,QTableView带进度条
类QabstractItemModel,QabstractListModel,QAbstractTableModel不保存数据,用户需要从这些类派生出子类,并在子类中定义某种数据结构来保存数据.与此不 ...
- CSS3通配符
在 CSS3 中,追加了三个属性选择器分别为: [att*=val] ----内容包含 [att^=val] ----开头匹配 [att$=val] ----结尾匹配 示例: <!DOCTYPE ...
- iOS 限制UITextField输入字符
开篇 之前做过一个即时通信的项目,需要限制输入框文本的字符个数,当时从网络上搜寻了几个方法,解决了需求,但是网络上的解决办法不是很全面:今天又遇到一个限制搜索框UISearchBar输入字符个数的问题 ...
- Hibernate常见错误整理
Hibernate常见错误合集 1.错误:object references an unsaved transient instance - save the transient instance ...