Подтвердить что ты не робот

Самый быстрый и эффективный способ предварительного заполнения базы данных на Android

Если вы хотите предварительно заполнить базу данных (SQLite) на Android, это не так просто, как можно подумать.

Итак, я нашел этот учебник, который часто упоминается здесь также в разделе "Переполнение стека".

Но мне не очень нравится такой способ предварительного заполнения базы данных, так как вы берете управление из обработчика базы данных и сами создаете файлы. Я бы предпочел не касаться файловой системы и позволить обработчику базы данных делать все сам по себе.

Итак, я думал, что можно создать базу данных в обработчике базы данных onCreate(), как обычно, но затем загрузить файл (.sql) из /assets, который содержит инструкции для заполнения значений:

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

Но вызов execSQL() в обработчике onCreate() не работает. Кажется, что файл /assets не должен иметь более 1 МБ, а execSQL() выполняет только первый оператор (Mike - Tiger).

Что бы вы сделали, предварительно заполнив базу данных?

4b9b3361

Ответ 1

Я предлагаю следующее:

  • Оберните всю вашу логику INSERT в транзакцию (BEGIN... COMMIT или через beginTransaction()... endTransaction() API)
  • Как уже было предложено, используйте API-интерфейсы связывания и объекты утилизации.
  • Не создавайте индексы до завершения этой объемной вставки.

Кроме того, посмотрите Более быстрые объемные вставки в sqlite3?

Ответ 2

В вашем вопросе говорится, что вы хотите самый быстрый способ - но вам не нравится, как это делается в статье, - вы не хотите вручную заменять файл DB (хотя это может быть на самом деле быстрее, чем заполнение пустой БД с запросами).

У меня были такие же мысли, и я понял, что заселение с помощью операторов SQL и предварительная подготовка могут быть лучшим решением, но это зависит от того, как вы будете использовать БД.

В моем приложении мне нужно иметь около 2600 строк (с 4 столбцами) в БД при первом запуске - это данные для автозаполнения и несколько других вещей. Он будет изменен довольно редко (пользователи могут добавлять пользовательские записи, но большую часть времени им это не нужно) и довольно большой. Заполнение его из операторов SQL занимает не только значительно больше времени, но и больше места в APK (предполагая, что я буду хранить данные внутри него, иначе я мог бы загрузить его из Интернета).

Это очень простой случай ( "Большая" вставка может иметь место только один раз и только при первом запуске), и я решил пойти с копированием предварительно заполненного файла DB. Конечно, это может быть не самый лучший способ, но это быстрее. Я хочу, чтобы мои пользователи могли использовать приложение настолько быстро, насколько это возможно, и относиться к скорости как к приоритету - и им это действительно нравится. Напротив, я сомневаюсь, что они будут рады, когда приложение будет замедляться, потому что я думал, что более медленное и приятное решение на самом деле лучше.

Если вместо 2600 моя таблица будет иметь первоначально ~ 50 строк, я бы пошел с операторами SQL, так как разница в скорости и размере не была бы такой большой.

Вам нужно решить, какое решение лучше подходит вашему делу. Если вы предвидите любые проблемы, которые могут возникнуть в результате использования опции "prepopulated db" - не используйте ее. Если вы не уверены в этих проблемах, спросите, предоставив более подробную информацию о том, как вы будете использовать (и в конечном итоге, обновлять) содержимое БД. Если вы не совсем уверены, какое решение будет быстрее, сравните его. И не бойтесь этого метода копирования файлов - он может работать очень хорошо, если использовать его разумно.

Ответ 3

Я написал класс DbUtils, похожий на предыдущий. Это часть инструмента ORM greenDAO и доступна на github. Разница в том, что он попытается найти границы операторов, используя простое регулярное выражение, а не только окончание строк. Если вам приходится полагаться на файл SQL, я сомневаюсь, что существует более быстрый способ.

Но если вы можете предоставить данные в другом формате, это должно быть значительно быстрее, чем использование SQL script. Хитрость заключается в использовании скомпилированного заявления. Для каждой строки данных вы связываете анализируемые значения с оператором и выполняете оператор. И, конечно же, вам нужно сделать это внутри транзакции. Я бы порекомендовал простой формат разделенного разделителя (например, CSV), потому что его можно анализировать быстрее, чем XML или JSON.

Мы выполнили тесты производительности для greenDAO. Для наших тестовых данных мы имели скорость вставки около 5000 рядов в секунду. И по какой-то причине скорость снизилась до половины с Android 4.0.

Ответ 4

Вы можете получить свой торт и съесть его тоже. Вот решение, которое может как уважать использование вашего адаптера db, так и использовать простой (и намного более быстрый) процесс копирования для предварительно заполненной базы данных.

Я использую адаптер db на основе одного из примеров Google. Он включает внутренний класс dbHelper(), который расширяет класс Android SQLiteOpenHelper(). Хитрость заключается в переопределении метода onCreate(). Этот метод вызывается только тогда, когда помощник не может найти базу данных, на которую вы ссылаетесь, и ей нужно создать БД для вас. Это должно произойти только при первом вызове любой установки устройства, которая является единственным моментом, когда вы хотите скопировать БД. Поэтому переопределите его так:

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

Конечно, убедитесь, что вы сначала объявили и инициализировали этот флаг в DbHelper -

        private Boolean mNeedToCopyDb = false;

Теперь, в вашем методе open() dbAdapter вы можете проверить, нужно ли вам копировать БД. Если вы это сделаете, закройте помощника, скопируйте БД и затем, наконец, откройте новый помощник (см. Ниже код). Все будущие попытки открыть db с помощью адаптера db найдут вашу (скопированную) БД, и поэтому метод onCreate() внутреннего класса DbHelper не будет вызываться, а флаг mNeedToCopyDb останется ложным.

    /**
 * 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;
}

Просто поместите код для копирования своей базы данных внутри адаптера db в методе copyDatabase(), как описано выше. Вы можете использовать значение mDb, которое было обновлено первым экземпляром DbHelper (когда он создавал БД-заглушку), чтобы получить путь к использованию для вашего потока вывода при копировании. Постройте свой входной поток следующим образом

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

[примечание: если ваш файл DB слишком велик, чтобы скопировать в один gulp, а затем разбить его на несколько частей.]

Это работает очень быстро и помещает весь код доступа db (включая копирование БД, если необходимо) в ваш адаптер db.

Ответ 5

ye, активы, возможно, имеет ограничение по размеру, поэтому, если он превышает лимит, вы можете разрезать на большее количество файлов.

и 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 большой, вы можете создать базу данных вне сайта android (поддержка sqlite для windows, linux, так что вы можете создать базу данных в своих os и скопировать файл базы данных в вашу папку с ресурсами, если большой, вы можете его закрепить)

когда ваше приложение запускается, вы можете получить файл базы данных из активов, предназначенный для сохранения в папку базы данных вашего приложения (если вы его застегнете, вы можете распаковать ее в папку базы данных приложения)

Надежда может вам помочь -):

Ответ 6

Я использовал этот метод. Сначала создайте свою базу данных sqlite, есть несколько программ, которые вы можете использовать. Мне нравится SqliteBrowser. Затем скопируйте файл базы данных в свою папку с ресурсами. Затем вы можете использовать этот код в конструкторе 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(). Также проверять, существует ли файл каждый раз, вероятно, не самый лучший. В любом случае, как я сказал, это работает, это быстро и надежно.

Ответ 7

Если данные не являются приватными, просто запустите его на своем веб-сайте, а затем загрузите его при первом запуске. Таким образом, вы можете поддерживать его в актуальном состоянии. До тех пор, пока вы не забудете учитывать версию приложения, когда вы загружаете его на свой веб-сервер.