在AsyncTask doInBackground中创建14万行SQLite数据库需要很多很多分钟



在上周之前我还没有处理过SQLite数据库。我上次处理SQL是在许多年前,但我仍然掌握了它的要点。

下面的代码从名为dictionary.dicasset中读取140,000个单词,并将每个单词与其status一起插入SQLite数据库。我的预期是需要很长一段时间,但在7英寸的平板电脑上花了25分钟,仍然没有接近完成(在P上)。

我应该说,"嘿,它是一百万行的1/7。这需要一段时间。"但我可以在30秒内把所有14万字读成ArrayList<String>。我知道创建数据库有开销,但是很多很多分钟?

我应该说,"嗯,想想如果不使用AsyncTask需要多长时间",并接受它,因为它是一次性任务?但真的很讨厌,花了这么长时间。这是讨厌。

我应该说,"你为什么要用Scanner ?"难怪要花这么长时间?"然后做一些其他的asset访问?还是这不是真正的问题?

我也从来没有使用过AsyncTask。我是否滥用doInBackground ?这里有很多代码;并不是所有的都必须去那里,但是循环就是这样,并且有挂起。

使用database.Insert,这被称为"方便方法",是什么导致挂起?我应该使用Cursorquery代替吗?我不太确定我该怎么做。我的想法来自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;
    }
    ...
  }