在 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.dicasset 中读取140,000 个单词,并将每个单词与其status 一起插入到SQLite 数据库中。我的预期是这需要很长时间,但在 7 英寸平板电脑上大约需要 25 分钟,但仍未完成(P)。

我应该说,“嘿,这是一百万行的 1/7。这需要一段时间。”但我可以在 30 秒内将所有 140,000 个单词读入ArrayList<String>。我意识到创建数据库的开销很大,但是很多很多分钟?

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

我应该说,“你为什么使用Scanner?难怪它需要这么长时间?”并做一些其他的asset 访问?或者这不是真正的问题?

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

正在使用database.Insert,这被称为“方便方法”,是什么导致挂断?我应该改用Cursorquery 吗?我不完全确定我会怎么做。我的想法来自 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 行就将事务代码放入 doInBackgroundpublish 中。是的?不熟悉数据库事务,但将上面的代码理解为它们的“标准习语”。 (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&lt;String&gt; 中,这可能是一个可怕的计划,但男孩这一切都很快。我将努力消除庞大的数组列表并尝试仅依赖WHERE WORD LIKE ? 其中? 是用户的“模式”。这大约需要 7 秒,在应用程序离开内存后每个 onCreate 一次。 通过使用前面评论中描述的数据库索引和where 子句的强大功能,我消除了140,000 字的ArrayList。因此,任何通配符搜索几乎都是即时的。

以上是关于在 AsyncTask doInBackground 中创建 140,000 行 SQLite 数据库需要很多很多分钟的主要内容,如果未能解决你的问题,请参考以下文章

AsyncTask

AsyncTask

AsyncTask 替代背景图像下载? (AsyncTask ---------已弃用)

Android AsyncTask get()形成另一个AsyncTask()

Android面试Android异步任务AsyncTask

AsyncTask onCancelled(Object) 从未在 asyncTask.cancel(true) 之后调用;