在 AsyncTask doInBackground 中创建 140,000 行 SQLite 数据库需要很多很多分钟
Posted
技术标签:
【中文标题】在 AsyncTask doInBackground 中创建 140,000 行 SQLite 数据库需要很多很多分钟【英文标题】:Creating 140,000-row SQLite database in AsyncTask doInBackground taking many, many minutes 【发布时间】:2015-12-13 14:39:14 【问题描述】:上周之前我还没有处理过 SQLite 数据库。我上次处理 SQL 是多年前,但我仍然掌握了它的要点。
下面的代码从名为dictionary.dic
的asset
中读取140,000 个单词,并将每个单词与其status
一起插入到SQLite 数据库中。我的预期是这需要很长时间,但在 7 英寸平板电脑上大约需要 25 分钟,但仍未完成(P
)。
我应该说,“嘿,这是一百万行的 1/7。这需要一段时间。”但我可以在 30 秒内将所有 140,000 个单词读入ArrayList<String>
。我意识到创建数据库的开销很大,但是很多很多分钟?
我应该说,“好吧,想想如果不使用AsyncTask
需要多长时间”并接受它,因为这是一次性任务?但这真的很讨厌,花了这么长时间。很不爽。
我应该说,“你为什么使用Scanner
?难怪它需要这么长时间?”并做一些其他的asset
访问?或者这不是真正的问题?
我也从未使用过AsyncTask
。我在滥用doInBackground
吗?我那里有很多代码;不是所有的人都必须去那里,但循环就是这样并且有挂断。
正在使用database.Insert
,这被称为“方便方法”,是什么导致挂断?我应该改用Cursor
和query
吗?我不完全确定我会怎么做。我的想法来自 Deitel 在“android for Programmers--App Driven...”中的“通讯录”应用程序,但他的数据库一开始是空的。
我已经考虑了很多。我只需要有经验的人来看看并说,“好吧,这就是你的问题。”如果没有任何关于是否有帮助的指导,我无法证明开始重做我想到的所有事情是合理的。
public class DatabaseConnector //extends ArrayList<String>
public static Cursor cursor ;
Scanner scDict;
InputStream stream = null;
Context mContext;
AssetManager mAssets;
public static final String DATABASE_NAME = "Dictionary";
public static final String TABLE_NAME = "wordlist";
public static final String WORD_COLUMN_NAME = "word";
public static final String STATUS_COLUMN_NAME = "status";
public static final String [] columns = new String[]WORD_COLUMN_NAME, STATUS_COLUMN_NAME;
private DatabaseOpenHelper ___databaseOpenHelper; // creates the database
private SQLiteDatabase ___database; // for interacting with the database
public DatabaseConnector(Context _context, AssetManager assets)
mContext = _context;
mAssets = assets;
___databaseOpenHelper = new DatabaseOpenHelper(_context, DATABASE_NAME, null, 1);
Log.w("DB connected", ___databaseOpenHelper.getDatabaseName());
createDbIfNecessary();
;
public void open() throws SQLException // opens/creates
___database = ___databaseOpenHelper.getWritableDatabase(); // create OR open
public void createDbIfNecessary()
this.open();
if(getDbCount() < 140000)
try stream = mAssets.open("dictionary.dic");
catch (IOException e) System.out.println(Arrays.toString(e.getStackTrace()));
MainActivity.setLblProgress("This one-time task takes awhile: loading letter... ");
LoadWords loadWords = new LoadWords();
loadWords.execute((Object[]) null);
this.close();
public void close()
if(___database != null)
___database.close();
public int getDbCount()
this.open();
return ___database.query(TABLE_NAME, columns, null, null, null, null, null).getCount();
public long insertWord(String _word)
ContentValues
__newWord;
__newWord = new ContentValues();
__newWord.put(WORD_COLUMN_NAME, _word);
__newWord.put(STATUS_COLUMN_NAME, true);
long __row = ___database.insert(TABLE_NAME, null, __newWord);
return __row; // -1 if can't insert
//////////////////////////////////////////////////////////////////////////////////////////////////
private class DatabaseOpenHelper extends SQLiteOpenHelper
public DatabaseOpenHelper(Context _context, String _name, CursorFactory _factory, int _version)
super(_context, _name, _factory, _version);
@Override public void onCreate(SQLiteDatabase _db)
_db.execSQL( "CREATE TABLE " + TABLE_NAME +
"("
+ WORD_COLUMN_NAME + " TEXT primary key , " //not null, "
+ STATUS_COLUMN_NAME + " BOOLEAN" +
");"
); // execute query to create the ___database
// end class DatabaseOpenHelper
//////////////////////////////////////////////////////////////////////////////////////////////////
private class LoadWords extends AsyncTask<Object, Integer, Void>
@Override
protected Void doInBackground(Object... params)
long k = 0;
scDict = new Scanner(stream).useDelimiter("\r\n");
long count = getDbCount();
Log.w("Start load at " , "" + count);
String s = "";
while(k++ < count)
s = scDict.next();
Log.w("Add after " , s);
while (scDict.hasNext())
s = scDict.next();
publishProgress((Integer)(int)s.charAt(0));
insertWord(s) ;
return null;
protected void onProgressUpdate(Integer... progress)
int c = (int)progress[0];
MainActivity.setLastLetterProcessed((char) c);
@Override
protected void onPostExecute(Void x)
MainActivity.popupMessage("Database has been created", mContext);
// end class DatabaseConnector
【问题讨论】:
使用三个下划线字符表示局部变量的任何特殊原因? 尝试批量插入。参考此示例代码techrepublic.com/blog/software-engineer/… @MuhammadBabar——这基本上就是 Commonsware 所建议的。我已经实施了这项技术(请参阅我的答案)。 @DSlomer64 仔细查看链接发现bulkInsertOneHundredRecords
这个方法不一样!
@MuhammadBabar--好的,我会的。谢谢你的坚持。昨晚我看的很快,因为我对速度很满意;
【参考方案1】:
您正在尝试执行 140,000 个单独的数据库事务。这可能需要数周时间。
相反,要么将你的整个事物包装在一个事务中,要么将插入批处理到事务中(例如,每 1000 个单词)。您可以使用这个伪 Java 创建自己的事务边界:
db.beginTransaction();
try
// do your SQL work here
db.setTransactionSuccesful();
catch (Exception e)
// logging, event bus message to UI, whatever
finally
db.endTransaction();
【讨论】:
@Commonsware--感谢您随时提供的帮助。 (1) 我认为您是说每添加 1000 行就将事务代码放入doInBackground
和 publish
中。是的?不熟悉数据库事务,但将上面的代码理解为它们的“标准习语”。 (2) 我可以像现在这样使用insert
吗?
@DSlomer64:“我想你在说......”——嗯,你的数据库 I/O 需要在某种形式的后台线程上。我是说您不需要为单个 INSERT
语句执行单个事务,而是在较大的事务中执行许多 INSERT
语句。 “我可以像现在这样使用 insert 吗?” -- 只要你开始将它们分组到事务中,当然可以。
嘿...是的,忘记了远离 GUI 线程的原则 #1。对此没有太多考虑。我很高兴能继续这样做。
@Commonsware--哦,是的。非常快。不到一分钟;准备调整。谢谢!!!!【参考方案2】:
感谢@Commonsware,现在可以在一分钟内加载 140,000 条记录,而不是一小时内。我所做的只是使用他的“p-code”将我的insert
用一个 1000 计数的循环包围:
protected Void doInBackground(Object... params)
...
scDict = new Scanner(stream).useDelimiter("\r\n");
long count = getDbCount();
while (k++ < count)
s = scDict.next();
while (scDict.hasNext())
//////////////////////////////////////////////////////// start insert
int ki = 0;
try
___database.beginTransaction();
while (ki < MAX_TRANSACTIONS && scDict.hasNext())
//////////////////////////////////////////////////////// end
insertWord(scDict.next());
//////////////////////////////////////////////////////// start insert
++ki;
___database.setTransactionSuccessful();
catch(Exception e) Log.w("Exception",e);
finally
___database.endTransaction();
publishProgress((Integer) (int) s.charAt(0));
//////////////////////////////////////////////////////// end
return null;
...
【讨论】:
再次感谢@Commonsware 的完美建议,因为我能够在应用程序的另一部分再次实现它,以加快将数据库单词加载到巨大的ArrayList<String>
中,这可能是一个可怕的计划,但男孩这一切都很快。我将努力消除庞大的数组列表并尝试仅依赖WHERE WORD LIKE ?
其中?
是用户的“模式”。这大约需要 7 秒,在应用程序离开内存后每个 onCreate
一次。
通过使用前面评论中描述的数据库索引和where
子句的强大功能,我消除了140,000 字的ArrayList
。因此,任何通配符搜索几乎都是即时的。以上是关于在 AsyncTask doInBackground 中创建 140,000 行 SQLite 数据库需要很多很多分钟的主要内容,如果未能解决你的问题,请参考以下文章
AsyncTask 替代背景图像下载? (AsyncTask ---------已弃用)
Android AsyncTask get()形成另一个AsyncTask()
AsyncTask onCancelled(Object) 从未在 asyncTask.cancel(true) 之后调用;