在Android中预先填充数据库的最快,最有效的方法

时间:2012-01-06 01:09:33

标签: android sql database sqlite

如果你想在Android中预先填充数据库(SQLite),这并不像人们想象的那么容易。

所以我找到了this tutorial,这也经常在Stack Overflow上引用。

但是我不喜欢这种预先填充数据库的方式,因为你从数据库处理程序中获取控件并自己创建文件。我宁愿不触摸文件系统,让数据库处理程序自己完成所有事情。

所以我认为可以做的就是像往常一样在数据库处理程序的onCreate()中创建数据库,然后从/ assets加载一个文件(.sql),其中包含要填充值的语句:

INSERT INTO testTable (name, pet) VALUES ('Mike', 'Tiger');
INSERT INTO testTable (name, pet) VALUES ('Tom', 'Cat');
...

但是在处理程序的onCreate()中调用execSQL()并不能真正起作用。似乎/ assets文件的大小不得超过1MB,而execSQL()只执行第一个语句(Mike - Tiger)。

你会做什么做预先填充数据库?

7 个答案:

答案 0 :(得分:5)

我建议如下:

  1. 将您的所有INSERT逻辑包装到一个交易中(BEGIN... COMMIT,或通过beginTransaction() ... endTransaction() API)
  2. 如前所述,使用绑定API和回收对象
  3. 在此批量插入完成后之前,请勿创建任何索引。
  4. 另外,请查看Faster bulk inserts in sqlite3?

答案 1 :(得分:4)

您的问题表明,您希望以最快的方式 - 但您不喜欢文章中的方式 - 您不想手动替换数据库文件(即使它实际上可能比填充更快)带查询的空DB。

我有同样的想法 - 我发现,通过SQL语句填充和预填充都可以是最佳解决方案 - 但这取决于您使用数据库的方式。

在我的应用程序中,我需要在第一次运行时在DB中有大约2600行(有4列) - 它是自动完成的数据和其他一些东西。它将很少被修改(用户可以添加自定义记录,但大部分时间 - 他们不需要)并且非常大。从SQL语句填充它不仅需要更多的时间,而且还需要更多的空间(假设我将数据存储在其中,或者我可以从互联网上下载)。

这是一个非常简单的情况(“大”插入只能在第一次启动时发生一次)我决定使用复制预先填充的DB文件。当然,它可能不是最好的方式 - 但速度更快。我希望我的用户能够尽可能快地使用应用程序,并将速度视为优先事项 - 他们非常喜欢它。相反,我怀疑当应用程序放慢速度时他们会很高兴,因为我认为更慢更好的解决方案实际上更好。

如果不是2600,我的表最初会有50行,我会使用SQL语句,因为速度和大小的差异不会那么大。

您必须更好地决定哪种解决方案适合您的情况。如果您预见到使用“预填充db”选项可能引起的任何问题 - 请勿使用它。如果您不确定这些问题 - 请询问,提供有关如何使用(并最终升级)数据库内容的更多详细信息。如果你不确定哪种解决方案会更快 - 那么就要对它进行基准测试。并且不要害怕复制文件方法 - 如果明智地使用它可以很好地工作。

答案 2 :(得分:2)

我写了一个类似于上一个答案的DbUtils类。它是ORM工具greenDAO的一部分,可在github上找到。不同之处在于它将尝试使用简单的正则表达式来查找语句边界,而不仅仅是行结尾。如果你必须依赖SQL文件,我怀疑有更快的方法。

但是,如果您可以提供其他格式的数据,那么它应该比使用SQL脚本快得多。诀窍是使用compiled statement。对于每个数据行,将已解析的值绑定到语句并执行该语句。当然,您需要在事务中执行此操作。我建议使用简单的分隔符分隔文件格式(例如CSV),因为它可以比XML或JSON更快地解析。

我们为greenDAO做了一些performance tests。对于我们的测试数据,我们的插入速率约为每秒5000行。出于某种原因,rate dropped to half with Android 4.0

答案 3 :(得分:2)

你也可以吃蛋糕。这是一个解决方案,它既可以尊重数据库适配器的使用,也可以对预先填充的数据库使用简单(且速度更快)的复制过程。

我正在使用基于Google的一个示例的数据库适配器。它包含一个内部类dbHelper(),它扩展了Android的SQLiteOpenHelper()类。诀窍是覆盖它的onCreate()方法。只有当帮助程序找不到您正在引用的数据库并且必须为您创建数据库时,才会调用此方法。这应该仅在第一次在任何给定的设备安装上调用时发生,这是您唯一要复制数据库的时间。所以像这样覆盖它 -

    @Override
    public void onCreate(SQLiteDatabase db) {
        mNeedToCopyDb = true;
    }

当然要确保你先在DbHelper中声明并初始化了这个标志 -

        private Boolean mNeedToCopyDb = false;

现在,在dbAdapter的open()方法中,您可以测试是否需要复制数据库。如果你这样做,然后关闭帮助程序,复制数据库,然后最后打开一个新的帮助程序(见下面的代码)。将来使用db适配器打开数据库的所有尝试都将找到您的(复制的)DB,因此不会调用内部DbHelper类的onCreate()方法,并且标志mNeedToCopyDb将保持为false。

    /**
 * Open the database using the adapter. If it cannot be opened, try to
 * create a new instance of the database. If it cannot be created,
 * throw an exception to signal the failure.
 * 
 * @return this (self reference, allowing this to be chained in an
 *         initialization call)
 * @throws SQLException if the database could neither be opened nor created
 */
public MyDbAdapter open() throws SQLException {
    mDbHelper = new DatabaseHelper(mCtx);
    mDb = mDbHelper.getReadableDatabase();

    if (mDbHelper.mNeedToCopyDb == true){
        mDbHelper.close();
        try {
            copyDatabase();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            mDbHelper = new DatabaseHelper(mCtx);
            mDb = mDbHelper.getReadableDatabase();
        }
    }
    return this;
}

只需放置一些代码,在上面使用的名为copyDatabase()的方法中,在db适配器内部进行数据库复制。您可以使用由DbHelper的第一个实例(创建存根数据库时)更新的mDb值,以便在执行复制时获取用于输出流的路径。 像这样构建你的输入流

dbInputStream = mCtx.getResources().openRawResource(R.raw.mydatabase);

[注意:如果您的数据库文件太大而无法一次性复制,那么只需将其分解为几个部分。]

这非常快,并且将所有数据库访问代码(包括需要时复制数据库)放入数据库适配器。

答案 4 :(得分:0)

你们,资产可能有大小限制,所以如果超过限制,你可以减少更多文件。

和exesql支持更多的sql语句,这里举个例子:

    BufferedReader br = null;
    try {
        br = new BufferedReader(new InputStreamReader(asManager.open(INIT_FILE)), 1024 * 4);
        String line = null;
        db.beginTransaction();
        while ((line = br.readLine()) != null) {
            db.execSQL(line);
        }
        db.setTransactionSuccessful();
    } catch (IOException e) {
        FLog.e(LOG_TAG, "read database init file error");
    } finally {
        db.endTransaction();
        if (br != null) {
            try {
                br.close();
            } catch (IOException e) {
                FLog.e(LOG_TAG, "buffer reader close error");
            }
        }
    }

上面的示例要求INIT_FILE需要每行都是一个sql语句

另外,如果您的sql语句文件很大,您可以创建数据库 out of android (sqlite支持windows,linux,这样您就可以在您的操作系统中创建数据库,并复制数据库文件到你的资产文件夹,如果大,你可以压缩它)

当你的应用程序运行时,你可以从资产中获取数据库文件,并将其保存到应用程序的数据库文件夹中(如果你压缩它,你可以解压缩到应用程序的数据库文件夹)

希望可以帮助你 - ):

答案 5 :(得分:0)

我用过这种方法。首先创建你的sqlite数据库你可以使用一些程序,我喜欢SqliteBrowser。然后将数据库文件复制到assets文件夹中。然后,您可以在SQLiteOpenHelper的构造函数中使用此代码。

final String outFileName = DB_PATH + NAME;

        if(! new File(outFileName).exists()){
            this.getWritableDatabase().close();
            //Open your local db as the input stream
            final InputStream myInput = ctx.getAssets().open(NAME, Context.MODE_PRIVATE);

            //Open the empty db as the output stream
            final OutputStream myOutput = new FileOutputStream(outFileName);
            //final FileOutputStream myOutput = context.openFileOutput(outFileName, Context.MODE_PRIVATE);

            //transfer bytes from the inputfile to the outputfile
            final byte[] buffer = new byte[1024];
            int length;
            while ((length = myInput.read(buffer))>0){
                myOutput.write(buffer, 0, length);
            }

            //Close the streams
            myOutput.flush();
            ((FileOutputStream) myOutput).getFD().sync();
            myOutput.close();
            myInput.close();
        }
        } catch (final Exception e) {
            // TODO: handle exception
        }

DB_PATH类似于/data/data/com.mypackage.myapp/databases /

NAME是您选择的任何数据库名称“mydatabase.db”

我知道这段代码有很多改进,但它工作得非常好而且非常快。所以我一个人离开了。像这样在onCreate()方法中可能会更好。另外,每次检查文件是否存在可能不是最好的。无论如何,就像我说它有效,它快速可靠。

答案 6 :(得分:0)

如果数据不是私有的,那么只需将其托管在您的网站上,然后在首次运行时下载。这样你可以保持最新。只要您记得在将应用版本上传到网络服务器时将其考虑在内。