您的位置:首页 > 移动开发 > Android开发

Android数据库一些源码分析

2015-05-21 12:19 281 查看
对于批量数据插入这种最常见的情况来说,我们来看两种实现方式(两种都用了事务)。

下面这种应该是最多人使用的插入数据的方法:

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里耗时的!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: