在上周之前我还没有处理过SQLite数据库。我上次处理SQL是在许多年前,但我仍然掌握了它的要点。
下面的代码从名为dictionary.dic
的asset
中读取140,000个单词,并将每个单词与其status
一起插入SQLite数据库。我的预期是需要很长一段时间,但在7英寸的平板电脑上花了25分钟,仍然没有接近完成(在P
上)。
我应该说,"嘿,它是一百万行的1/7。这需要一段时间。"但我可以在30秒内把所有14万字读成ArrayList<String>
。我知道创建数据库有开销,但是很多很多分钟?
我应该说,"嗯,想想如果不使用AsyncTask
需要多长时间",并接受它,因为它是一次性任务?但真的很讨厌,花了这么长时间。这是讨厌。
我应该说,"你为什么要用Scanner
?"难怪要花这么长时间?"然后做一些其他的asset
访问?还是这不是真正的问题?
我也从来没有使用过AsyncTask
。我是否滥用doInBackground
?这里有很多代码;并不是所有的都必须去那里,但是循环就是这样,并且有挂起。
使用database.Insert
,这被称为"方便方法",是什么导致挂起?我应该使用Cursor
和query
代替吗?我不太确定我该怎么做。我的想法来自Deitel在"面向程序员的Android——应用驱动…"中的"地址簿"应用,但他的数据库一开始是空的。
我已经仔细考虑过了。我只是需要一个有经验的人来看看,然后说,"好吧,这就是你的问题。"如果没有一些指导是否有帮助,我无法证明重新做所有我想过的事情是合理的。
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("rn");
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
您正在尝试执行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, 14万条记录现在在一分钟内加载,而不是在一小时内。我所做的就是使用他的"p-code"将我的insert
与1000个计数的循环包围:
protected Void doInBackground(Object... params) {
...
scDict = new Scanner(stream).useDelimiter("rn");
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;
}
...
}