Android数据库一些源码分析
2015-05-21 12:19
281 查看
对于批量数据插入这种最常见的情况来说,我们来看两种实现方式(两种都用了事务)。
下面这种应该是最多人使用的插入数据的方法:
再看一种比较少用的插入方法
然后我们分别用这两个方法 来向数据库里面插入一万条数据 看看耗时多少。为了演示效果更加突出一点,我录制了一个GIF,同时,
这2个方法我也没有用子线程来操作他,直接在ui线程上操作 所以看起来效果会比较突出一些(但是自己写代码的时候千万别这么写小心ANR)。
可以看出来后者耗时几乎只有前者的 一半(所以以后大家在做大批量数据插入的时候可以考虑后者的实现方式)。我们来看看源代码为啥会这样。
首先看前者的实现方法源码
我们发现 前者的实现 实际上最后也是通过SQLiteStatement 这个类是操作的。
而后者不过是
所以实际上前者之所以比后者耗时 应该是下面这段代码的原因:
实际上就是多了一个字符串处理的函数。这就是为什么前者耗时要比后者多。因为实际上直接调用executeSql的时候
他里面是先做字符串处理然后再调用SQLiteStatement来执行,这个过程当然是比我们直接调用SQLiteStatement
来执行速度慢的。
我们首先来看一下下面这个函数
一个很常见的,多表查询的函数,有些人可能会奇怪为啥在这个地方我要加那么多日志。实际上如果你t1和t3的数据都很多的话,这个查询是可以预料到的会非常耗时。
很多人都会以为这个耗时是在下面这条语句做的:
但是实际上这个查询耗时是在你第一调用
来做的,有兴趣的同学可以自己试一下,我们这里就不帮大家来演示这个效果了,但是可以帮助大家分析一下源代码为什么会是这样奇怪的结果?
我们首先来分析一下rawQuery 这个函数
看一下24行,发现是构造了一个driver对象 然后调用这个driver对象的query方法
我们继续跟踪源代码
发现这个返回的cursor实际上就是直接new出来的一个对象
所以看到这里我们就能确定的是rawquery这个方法 返回的cursor实际上就是一个对象,并没有任何真正调用sql的地方。
然后我们来看看我们怀疑的moveToNext这个方法因为从日志上看耗时的地方在第一次调用他的时候,所以我们怀疑真正调用查询sql的地方
在这个函数里面被触发。
看一下那个getcount方法
发现如果满足某个条件的话 就调用fillwindow这个方法,我们来看看是什么条件
看到这就明白了,如果你默认的mCount为-1的话就代表你这个cursor里面还没有查过吗,所以必须要调用fillwindow方法
我们来看看这个query是什么
看看他的fillwindow方法
一目了然,其实就是rawquery返回的是一个没有意义的cursor对象里面什么都没有,当你调用movetonext之类的方法的时候,
会判断是否里面没有数据 如果有数据就返回你要的数据,如果没有的话,实际上最终调用的就是SQLiteQuery这个类的fillwindow方法
来最终执行你写的sql语句~~耗时也就是在这里耗时!!!!!切记!不是在rawquery里耗时的!
下面这种应该是最多人使用的插入数据的方法:
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里耗时的!
相关文章推荐
- 开源中国 OsChina Android 客户端源码分析(8)数据库Sqlite
- Android 轻量级ORM数据库开源框架ActiveAndroid 源码分析
- Android数据库greenDAO框架用法和源码分析
- Android数据库ORM框架用法、源码和性能比较分析
- Android ORM数据库之GreenDao使用教程及源码分析
- Android源码分析之仿OrmLite数据库框架
- Android ORM数据库之OrmLite使用框架及源码分析
- android Xutils 数据库操作源码分析
- Android数据库greenDAO框架用法和源码分析
- 对Android广播接收与发出机制的一些AMS以外的源码分析
- Android开发之getMeasuredWidth和getWidth区别从源码分析
- Android源码-Android系统启动源码分析
- Android源码分析工具及方法
- Android缓存源码分析(DiskLruCache,LruCache)
- Android探索之旅(第十二篇)HashMap,ArrayMap,SparseArray源码分析及性能对比
- Android Tint的使用及源码分析
- android源码蓝牙协议分析
- Android关机流程源码分析
- Android recyclerview源码分析(一)
- Android Handler如何实现线程间通信,源码分析。