Sqlite
发布时间:2022-05-22 发布网站:大佬教程 code.js-code.com
大佬教程收集整理的这篇文章主要介绍了SQLite多线程读写实践及常见问题总结,大佬教程大佬觉得挺不错的,现在分享给大家,也给大家做个参考。
基本操作的部分,大家都很熟悉了,这里根据个人切身经验,总结了一些经常遇到的,也需要注意的一些问题,与大家分享,水平有限,不妥或者错误的地方还望指出。
sqlite
实质上是将数据写入一个文件,通常情况下,在应用的包名下面都能找到
xxx.db
的文件,拥有
root
权限的手机,可以通过
adb sHell
,看到
data/data/
packagename
/databases/xxx.db
这样的文件。
我们可以得知
sqlite
是文件级别的锁:多个线程可以同时读,但是同时只能有一个线程写。
AndroID
提供了
sqliteOpenHelper
类,加入
Java
的锁机制以便调用。
如果多线程同时读写(这里的指不同的线程用使用的是不同的
Helper
实例),后面的就会遇到
androID.database.sqlite.sqliteException: database is locked
这样的异常。
对于这样的问题,解决的办法就是
keep single sqlite connection,保持单个sqliteOpenHelper实例,同时对所有数据库操作的方法添加synchronized
关键字。
如下所示:
复制内容到剪贴板
代码:
public class DatabaseHelper extends sqliteOpenHelper {
public static final String TAG = "DatabaseHelper";
private static final String db_name = "practice.db";
private static final int DB_VERSION = 1;
private Context mContext;
private static DatabaseHelper mInstance;
private DatabaseHelper(Context context) {
super(context,db_name,null,DB_VERSION);
}
public synchronized static DatabaseHelper geTinstance(Context context) {
if (mInstance == null) {
mInstance = new DatabaseHelper(context);
}
return mInstance;
}
@OverrIDe
public voID onCreate(sqliteDatabase db) {
// Todo auto-generated method stub
}
@OverrIDe
public voID onUpgrade(sqliteDatabase db,int oldVersion,int newVersion) {
// Todo auto-generated method stub
}
public synchronized voID querymethod() {
sqliteDatabase readableDatabase = getReadableDatabase();
//read operation
}
public voID updateMethod() {
sqliteDatabase writableDatabase = getWritableDatabase();
//update operation
}
}
AndroID为我们提供了sqliteOpenHelper类,我们可以通过getWritableDatabase
或者
getReadableDatabase
拿到
sqliteDatabase
对象,然后执行相关方法。这
2
个方法名称容易给人误解,我也在很长的一段时间内想当然的认为
getReadabedatabase
就是获取一个只读的数据库,可以获取很多次,多个线程同时读,用完就关闭,实际上
getReadableDatabase
先以读写方式打开数据库,如果数据库的磁盘空
间满了,就会打开失败,当打开失败后会继续尝试以只读方式打开数据库。
代码:
public synchronized sqliteDatabase getReadableDatabase() {
if (mDatabase != null && mDatabase.isopen()) {
return mDatabase;// The database is already open for business
}
if (mIsInitializing) {
throw new IllegalStateException("getReadableDatabase called recursively");
}
try {
return getWritableDatabase();
} catch (sqliteException E) {
if (mname == null) throw e;// Can't open a temp database read-only!
Log.e(tag,"Couldn't open " + mname + " for wriTing (will try read-only):",E);
}
sqliteDatabase db = null;
try {
mIsInitializing = true;
String path = mContext.getDatabasePath(mname).getPath();
db = sqliteDatabase.openDatabase(path,mFactory,sqliteDatabase.oPEN_Readonly);
if (db.getVersion() != mNewVersion) {
throw new sqliteException("Can't upgrade read-only database from version " +
db.getVersion() + " to " + mNewVersion + ": " + path);
}
onopen(db);
Log.w(tag,"Opened " + mname + " in read-only mode");
mDatabase = db;
return mDatabase;
} finally {
mIsInitializing = false;
if (db != null && db != mDatabasE) db.close();
}
}
在多线程中,如果第一个线程先调用getWritableDatabase,后面线程再次调用,或者第一个线程先调用getReadableDatabase,后面的线程调用getWritableDatabase,那么后面的这个方法是会失败的,因为数据库文件打开后会加锁,必须等前面的关闭后后面的调用才能正常执行,正是因为这个原因,可以1 Write+Many Read(有可能产生冲突,因为第一个getReadableDatabase有可能先于getWritableDatabase执行,导致后面的失败),也可以Many Read,但是不可能Many Write。所以使用单例加上同步的数据库操作方法,就不会出现死锁的问题,这部分例子请参照附件,多线程可以运行的很好,另外关于sqlite database locking collisions example,网上有很不错的一个例子,可以
这里
去下载。
其实我觉得理论上可以修改getReadableDatabase方法,打开的数据库都是Read Only的,这样就能同时1 Write+Many Read,只不过要保证打开之前,数据库要创建或者升级好,这样读操作就不会互斥写操作,效率相对更高。
关于数据库关闭的问题,在下面好的习惯中会专
门说明。
接触过数据库的人,对事务这个概念一定不陌生,它是原子性的,要么执行成功,执行一半失败后会回滚,这样就能保证数据的完整性。
sqliteDatabase
也提供了
transaction
的相关方法,常见用法:
使用事务对于批量更新有极大的好处,因为单次更新会频繁的调用数据库,曾经我同步过联系人,没使用事务之前,
300
个联系人写入自己的数据库大概需要
3~5
秒钟的时间,引入事务后,读取联系人的时间没有减少,但是所有更新的时间降为
200ms
级,提升极为明显。
在应用迭代多个版本后,随着功能的增加和改变,数据库改变是很常见的事情,由于数据库中的数据一般是需要永久保存的,应用更新后,用户不希望数据丢失,特别是如果应用有几十万,百万级的用户量,如果很粗鲁的丢弃旧版本数据库中数据,对用户体验是很不好的,如果你没有提供云端备份的方案,就需要为用户保留旧的数据,即便数据库结构要发生变化。
实际上多次数据库变动的升级是很痛苦的事情,要考虑每一个旧的版本,理论上用户可以从任何一个旧的版本直接升级到最新版本,我们需要考虑每一种情况。
在
onUpgrade
方法中,针对每一种版本号,先把旧的临时数据保存下来,删去旧的表,创建新表,然后将数据根据情况插入到新表中,不需要的字段可以丢弃,新增字段填默认值,数据可以临时存放到一个数组中,或者可以临时
cache
到文件中,最后将临时文件清空。
更新操作可以使用事务提高效率,另外需要知道的是
I/O
操作时耗时的,如果数据量较大,还需要放到单独的线程中处理,防止阻塞
UI
。
我们也经常会遇到数据库中需要初始化数据,比如城市,机场,号码归属地等信息,如果数据量不是很大,我们可以处理后放到
asset
或者
raw
文件下,创建数据库后导入进去,并且在
2.3
以前,
asset
中文件有大小限制,文件大小不能超过
1M
,否则
AssetManager
或
resources classes
方法来获取
inputStream
,将抛出
DEBUG/asset(1123): Data exceeds UNCOMPRESS_DATA_MAX
的
java.io.IOException
异常。
解决这个问题有
4
个方法:
1.改名称(最简单):
aapt
工具在打包
apk
文件时,会将资源文件压缩以减小安装包大小(
raw
文件夹下的资源则不受影响)。
但是可以通过修改文件成下面的扩展名,逃避检查。
代码:
/* these formats are already compressed,or don't compress well */
static const char* kNoCompressExt[] = {
".jpg",".jpeg",".png",".gif",
".wav",".mp2",".mp3",".ogg",".aac",
".mpg",".mpeg",".mID",".mIDi",".smf",".jet",
".rtttl",".imy",".xmf",".mp4",".m4a",
".m4v",".3gp",".3gpp",".3g2",".3gpp2",
".amr",".awb",".wma",".wmv"
};
2.压缩:
如果原文件能压缩到
1M
一下,可以先压缩成
zip
或者
rar
格式,然后解压将数据库文件释放到相应位置。
3.分割文件:
大的数据,分割成多个小数据文件,
info1.dat,info2.dat…
,分别读取这些文件数据插入数据库。
4.网络:
上面的几种方法都是将初始化数据放在安装包中,这样无疑会增加安装包大小,如果必要情况下,可以将数据放到服务器上,创建数据库后,通过http请求,获取JsON,XML数据或者数据库文件,然后经过处理入库。
1.关闭cursor
cursor如果不关闭,虽然不会导致出错,但是Log中会有错误提示,还是严谨点,Activity中有startManagingcursor的方法,Activity会在生命周期结束时关闭这些cursor,其他地方,我们则需要用完关闭,以前需要cursor的Adapter则需要在changecursor时判断关闭old cursor,在Activity的onDestory方法中关闭cursor。
2.关闭DatabaseHelper
在上述单例Helper例子中,其实一直没有关闭数据库,但是我们阅读getReadabedatabase和getWritableDatabas的方法,他们会关闭old sqliteDatabase的,我们只需要在Application的onTerminal方法中关闭即可,这样也能避免多线程中,一个线程关闭了数据库,导致其他线程使用的时候失败的问题。
实质上,数据库是一个文件引用,单例模式下,不关闭也不会出现问题,让它保持随单例的生命周期关闭就好了。
3.在循环外面获取columnIndex,如果表中列不是很多,每次查询又返回所有列的话,可以将列的index定义到table_columNS中去,这样每次获取指定列数据的话,就不用去查找index了。
4.数据库存放的数据类型
AndroID提供了多种数据存储的方法,文件,数据库,SharePreference,网络等,要根据情况选择合适的方式,不要把什么东西都往数据库中塞。
下面的几种情况就不适合放到数据库中:
1)图片等二进制数据:如果是图片的话,可以将文件名称或者路径保存到数据库中,真正的文件可以作为缓存文件保存在文件系统中。
2)临时数据:定位获取到的LOCATIOn,登录的Session等。
3)日志数据:可以写入文件中,通常是log_xxxx.txt。
转载地址:http://bbs.51cto.com/thread-990260-1.html
大佬总结
以上是大佬教程为你收集整理的SQLite多线程读写实践及常见问题总结全部内容,希望文章能够帮你解决SQLite多线程读写实践及常见问题总结所遇到的程序开发问题。
如果觉得大佬教程网站内容还不错,欢迎将大佬教程推荐给程序员好友。
本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。