Какая база данных для Android обеспечивает безопасность и небольшой размер файла базы данных? - PullRequest
2 голосов
/ 04 июня 2019

Я создаю автономное словарное приложение. С файлом базы данных 150000 строк. Я использовал Sqlite, но мне нужно защитить свою базу данных, поэтому я использую библиотеку SqlCipher для шифрования. Проблема, с которой я столкнулся после шифрования, заключалась в скорости чтения данных, и файл SqlCipher после шифрования не мог быть сжат. Размер файла базы данных значительно увеличен.
Sqlite (9 МБ) -> SqlCipher (93 МБ) .
Я также пытался использовать базу данных Realm. Он имеет высокую скорость чтения и хорошую безопасность. Тем не менее, файл базы данных также значительно увеличен в размере.
Sqlite (9 МБ) -> Область (50 МБ) .
Есть ли способ уменьшить размер базы данных? Существует еще одна база данных для Android, чтобы преодолеть вышеуказанные недостатки (безопасность, скорость, размер базы данных)?

Ответы [ 2 ]

2 голосов
/ 05 июня 2019

Возможно, вы могли бы рассмотреть возможность реализации собственного шифрования / дешифрования, а затем только частичного шифрования фактических конфиденциальных данных.

Например, в приведенном ниже демонстрационном коде используется базовый словарь (слово / определение) с 280000 определениями (хотя идублированные определения).Это занимает 20,9 МБ без шифрования и 36,6 МБ при шифровании.

enter image description here

  • mydb - незашифрованная версия
  • mydbenc - зашифрованная версия

  • Здесь опять данные, которые на самом деле хранятся, скажем, для более длинных определений слов, могут оказать весьма значительное влияние, с таким же количеством слов и определений, но с заметно более длинными определениями (в случае, когда одно из 14 определений (каждый повторяется 20000 раз), затем размер зашифрованной базы данных увеличился на 4 МБ, зашифрованная база данных также выросла примерно на 4 МБ). (в демонстрационной версии используется БД большего размера)

    • enter image description here

Таким образом, зашифрованный размер вашей базы данных будет около 20 МБ для ваших 150 000 строк.

Размер также будет зависеть от метода шифрования.В целом, чем слабее метод шифрования, тем меньше накладные расходы, но с более низким коэффициентом безопасности.

Чтобы преодолеть проблему поиска, пример приложения расшифровывает данные во временную таблицу при запуске.Это займет чуть меньше минуты, что может быть неприемлемо.

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

Пример кода состоит из двух помощников по базам данных, один для незашифрованной версии, используемой для сравнения, и зашифрованной версии.Оба используют одну и ту же таблицу с 3 столбцами id (которая не зашифрована), word и definition последние два зашифрованы в зашифрованной версии.

База данных, таблица и столбцы имен определяются через константы в классе с именем DBConstants в соответствии с: -

public class DBConstants {

    public static int FILTEROPTION_ANYWHERE = 0;
    public static int FILTEROPTION_MUSTMATCH = 1;
    public static int FILTEROPTION_STARTSWITH = 2;
    public static int FILTEROPTION_ENDSWITH = 4;

    public static final String DBName = "mydb";
    public static final int DBVERSION = 1;
    public static final String DECRYPTEXTENSION = "_decrypt";

    public static class MainTable {

        public static final String TBLNAME = "main";
        public static final String COL_ID = BaseColumns._ID;
        public static final String COl_WORD = "_word";
        public static final String COL_DEFINITION = "_definition";

        public static final String CRT_SQL = "CREATE TABLE IF NOT EXISTS " + TBLNAME +
                "(" +
                COL_ID + " INTEGER PRIMARY KEY," +
                COl_WORD + " TEXT," +
                COL_DEFINITION + " TEXT" +
                ")";
    }

    public static class DecrtyptedMainTable {
        public static final String TBLNAME = MainTable.TBLNAME + DECRYPTEXTENSION;
        public static final String CRT_SQL = "CREATE TEMP TABLE IF NOT EXISTS " + TBLNAME +
                "(" +
                MainTable.COL_ID + " INTEGER PRIMARY KEY," +
                MainTable.COl_WORD + " TEXT, " +
                MainTable.COL_DEFINITION + " TEXT " +
                ")";
        public static final String CRTIDX_SQL = "CREATE INDEX IF NOT EXISTS " +
                TBLNAME + "_index " +
                " ON " + TBLNAME +
                "(" + MainTable.COl_WORD + ")";
    }
}

A Word Класс используется для разрешения Word объектов для извлечения в соответствии с: -

public class Word {
    private long id;
    private String word;
    private String definition;

    public Word() {
        this.id = -1L;
    }

    public Word(String word, String definition) {
        this(-1L,word,definition);
    }

    public Word(Long id, String word, String definition) {
        this.id = id;
        this.word = word;
        this.definition = definition;
    }

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getWord() {
        return word;
    }

    public void setWord(String word) {
        this.word = word;
    }

    public String getDefinition() {
        return definition;
    }

    public void setDefinition(String definition) {
        this.definition = definition;
    }
}

Помощник по базам данных DBHelperStandard.java для незашифрованной базы данных (существует исключительно для целей сравнения): -

public class DBHelperStandard extends SQLiteOpenHelper {

    SQLiteDatabase db;

    public DBHelperStandard(Context context) {
        super(context, DBConstants.DBName, null, DBConstants.DBVERSION);
        db = this.getWritableDatabase();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(DBConstants.MainTable.CRT_SQL);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    public long insertWord(String word, String definition) {
        ContentValues cv = new ContentValues();
        cv.put(DBConstants.MainTable.COl_WORD,word);
        cv.put(DBConstants.MainTable.COL_DEFINITION,definition);
        return db.insert(DBConstants.MainTable.TBLNAME,null,cv);
    }

    public long insertWord(Word word) {
        return insertWord(word.getWord(),word.getDefinition());
    }

    public int deleteWord(long id) {
        String whereclause = DBConstants.MainTable.COL_ID + "=?";
        String[] whereargs = new String[]{String.valueOf(id)};
        return db.delete(DBConstants.MainTable.TBLNAME,whereclause,whereargs);
    }

    public int deleteWord(Word word) {
        return deleteWord(word.getId());
    }

    public int updateWord(long id, String word, String defintion) {

        ContentValues cv = new ContentValues();
        if (word != null && word.length() > 0) {
            cv.put(DBConstants.MainTable.COl_WORD,word);
        }
        if (defintion != null && defintion.length() > 0) {
            cv.put(DBConstants.MainTable.COL_DEFINITION,defintion);
        }
        if (cv.size() < 1) return 0;
        String whereclause = DBConstants.MainTable.COL_ID + "=?";
        String[] whereargs = new String[]{String.valueOf(id)};
        return db.update(DBConstants.MainTable.TBLNAME,cv,whereclause,whereargs);
    }

    public int updateWord(Word word) {
        return updateWord(word.getId(),word.getWord(),word.getDefinition());
    }

    public List<Word> getWords(String wordfilter, int filterOption, Integer limit) {
        ArrayList<Word> rv = new ArrayList<>();
        String whereclause = DBConstants.MainTable.COl_WORD + " LIKE ?";
        StringBuilder sb = new StringBuilder();
        switch (filterOption) {
            case 0:
                sb.append("%").append(wordfilter).append("%");
                break;
            case 1:
                sb.append(wordfilter);
                break;
            case 2:
                sb.append(wordfilter).append("%");
                break;
            case 4:
                sb.append("%").append(wordfilter);
        }
        String[] whereargs = new String[]{sb.toString()};
        if (wordfilter == null) {
            whereclause = null;
            whereargs = null;
        }
        String limitclause = null;
        if (limit != null) {
            limitclause = String.valueOf(limit);
        }
        Cursor csr = db.query(
                DBConstants.MainTable.TBLNAME,
                null,
                whereclause,
                whereargs,
                null,
                null,
                DBConstants.MainTable.COl_WORD,
                limitclause
        );
        while (csr.moveToNext()) {
            rv.add(new Word(
                    csr.getLong(csr.getColumnIndex(DBConstants.MainTable.COL_ID)),
                    csr.getString(csr.getColumnIndex(DBConstants.MainTable.COl_WORD)),
                    csr.getString(csr.getColumnIndex(DBConstants.MainTable.COL_DEFINITION))
                    ));
        }
        return rv;
    }
}

Помощник по базам данных DBHelperEncrypted.java для зашифрованной базы данных: -

public class DBHelperEncrypted extends SQLiteOpenHelper {

    private String secretKey;
    private String ivpParemeter;

    SQLiteDatabase db;
    public DBHelperEncrypted(Context context, String secretKey, String ivpParamter) {
        super(context, DBConstants.DBName + DBConstants.DECRYPTEXTENSION, null, DBConstants.DBVERSION);
        this.secretKey = secretKey;
        this.ivpParemeter = ivpParamter;
        db = this.getWritableDatabase();
    }

    @Override
    public void onCreate(SQLiteDatabase db) { db.execSQL(DBConstants.MainTable.CRT_SQL); }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    public long insertWord(String word, String definition) {
        ContentValues cv = new ContentValues();
        cv.put(DBConstants.MainTable.COl_WORD,EncryptDecrypt.encrypt(word,secretKey,ivpParemeter));
        cv.put(DBConstants.MainTable.COL_DEFINITION,EncryptDecrypt.encrypt(definition,secretKey,ivpParemeter));
        return db.insert(DBConstants.MainTable.TBLNAME,null,cv);
    }

    public long insertWord(Word word) {
        return insertWord(word.getWord(),word.getDefinition());
    }

    public int deleteWord(long id) {
        String whereclause = DBConstants.MainTable.COL_ID + "=?";
        String[] whereargs = new String[]{String.valueOf(id)};
        return db.delete(DBConstants.MainTable.TBLNAME,whereclause,whereargs);
    }

    public int deleteWord(Word word) {
        return deleteWord(word.getId());
    }

    public int updateWord(long id, String word, String defintion) {

        ContentValues cv = new ContentValues();
        if (word != null && word.length() > 0) {
            cv.put(DBConstants.MainTable.COl_WORD,EncryptDecrypt.encrypt(word,secretKey,ivpParemeter));
        }
        if (defintion != null && defintion.length() > 0) {
            cv.put(DBConstants.MainTable.COL_DEFINITION,EncryptDecrypt.encrypt(defintion,secretKey,ivpParemeter));
        }
        if (cv.size() < 1) return 0;
        String whereclause = DBConstants.MainTable.COL_ID + "=?";
        String[] whereargs = new String[]{String.valueOf(id)};
        return db.update(DBConstants.MainTable.TBLNAME,cv,whereclause,whereargs);
    }

    public int updateWord(Word word) {
        return updateWord(word.getId(),word.getWord(),word.getDefinition());
    }

    public List<Word> getWords(String wordfilter, int filterOption, Integer limit) {
        ArrayList<Word> rv = new ArrayList<>();
        String whereclause = DBConstants.MainTable.COl_WORD + " LIKE ?";
        StringBuilder sb = new StringBuilder();
        switch (filterOption) {
            case 0:
                sb.append("%").append(wordfilter).append("%");
                break;
            case 1:
                sb.append(wordfilter);
                break;
            case 2:
                sb.append(wordfilter).append("%");
                break;
            case 4:
                sb.append("%").append(wordfilter);
        }
        String[] whereargs = new String[]{sb.toString()};
        String limitclause = null;
        if (limit != null) {
            limitclause = String.valueOf(limit);
        }
        Cursor csr = db.query(
                DBConstants.DecrtyptedMainTable.TBLNAME,
                null,
                whereclause,
                whereargs,
                null,
                null,
                DBConstants.MainTable.COl_WORD,
                limitclause
        );
        while (csr.moveToNext()) {
            rv.add(
                    new Word(
                            csr.getLong(csr.getColumnIndex(DBConstants.MainTable.COL_ID)),
                            csr.getString(csr.getColumnIndex(DBConstants.MainTable.COl_WORD)),
                            csr.getString(csr.getColumnIndex(DBConstants.MainTable.COL_DEFINITION))
                    )
            );
        }
        return rv;
    }

    public void buildDecrypted(boolean create_index) {
        db.execSQL(DBConstants.DecrtyptedMainTable.CRT_SQL);
        Cursor csr = db.query(DBConstants.MainTable.TBLNAME,null,null,null,null,null,null);
        ContentValues cv = new ContentValues();
        while (csr.moveToNext()) {
            cv.clear();
            cv.put(DBConstants.MainTable.COL_ID,csr.getLong(csr.getColumnIndex(DBConstants.MainTable.COL_ID)));
            cv.put(DBConstants.MainTable.COl_WORD,
                    EncryptDecrypt.decrypt(csr.getString(csr.getColumnIndex(DBConstants.MainTable.COl_WORD)),secretKey,ivpParemeter));
            cv.put(DBConstants.MainTable.COL_DEFINITION,
                    EncryptDecrypt.decrypt(csr.getString(csr.getColumnIndex(DBConstants.MainTable.COL_DEFINITION)),secretKey,ivpParemeter));
            db.insert(DBConstants.DecrtyptedMainTable.TBLNAME,null,cv);
        }
        csr.close();
        if (create_index) {
            db.execSQL(DBConstants.DecrtyptedMainTable.CRTIDX_SQL);
        }
    }
}
  • Основное отличие состоит в том, что (вероятно, нетиспользовать, как может показаться, что база данных отправлена), вставлять и обновлять методы шифрования данных, а также включать метод buildDecrypted , который создает временную таблицу из зашифрованной таблицы.Временная таблица, используемая для поиска и извлечения данных.

    • , будучи временной таблицей, будет удалена при закрытии базы данных.Обычно вы закрываете базу данных только после завершения работы приложения.

Шифрование и дешифрование выполняется классом EncryotDecrypt согласно EncryptDecrypt.java : -

public class EncryptDecrypt {
    public static Cipher cipher;

    /**
     * Encryption, irrespective of the USER type, noting that this should
     * only be used in conjunction with an EncryptDecrypt instance created
     * using the 2nd/extended constructor
     *
     * @param toEncrypt     The string to be encrypted
     * @return              The encrypted data as a string
     */
    public static String encrypt(String toEncrypt, String secretKey, String ivParameterSpec) {
        byte[] encrypted;
        try {
            if (cipher == null) {
                cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            }
            if (secretKey.length() < 16) {
                secretKey = (secretKey + "                ").substring(0,16);
            }
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(),"AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE,secretKeySpec,new IvParameterSpec(ivParameterSpec.getBytes()));
            encrypted = cipher.doFinal(toEncrypt.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return Base64.encodeToString(encrypted,Base64.DEFAULT);
    }

    /**
     * Decrypt an encrypted string
     * @param toDecrypt     The encrypted string to be decrypted
     * @return              The decrypted string
     */
    public static String decrypt(String toDecrypt, String secretKey, String ivParameterSpec)  {
        byte[] decrypted;
        try {
            if (cipher == null) {
                cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            }
            if (secretKey.length() < 16) {
                secretKey = (secretKey + "                ").substring(0,16);
            }
            SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getBytes(),"AES/CBC/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE,secretKeySpec,new IvParameterSpec(ivParameterSpec.getBytes()));
            decrypted = cipher.doFinal(Base64.decode(toDecrypt,Base64.DEFAULT));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return new String(decrypted);
    }
}
  • Примечание. Я ни в коем случае не эксперт, но я уверен, что вы могли бы относительно легко реализовать менее / более безопасные методы шифрования.

Последнее,все это вместе для этой демонстрации, это MainActivity.java : -

public class MainActivity extends AppCompatActivity {

    public static final String SK = "mysecretkey";
    public static final String SALT = "124567890ABCDEFG";
    DBHelperEncrypted mDBE;
    DBHelperStandard mDBS;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDBE = new DBHelperEncrypted(this,SK,SALT);
        mDBS = new DBHelperStandard(this);

        //Stage 1 - Build the demo databases
        ArrayList<Word> wordsanddefinitions = new ArrayList<>();
        for (int i=0; i < 20000; i ++) {
            wordsanddefinitions.add(new Word("Apple","Something that falls on peoples heads that causes them to discover gravity."));
            wordsanddefinitions.add(new Word("Bachelor","An unmarried man."));
            wordsanddefinitions.add(new Word("Bachelor","A person who has been awarded a bachelor's degree."));
            wordsanddefinitions.add(new Word("Bachelor","A fur seal, especially a young male, kept from the breeding grounds by the older males."));
            wordsanddefinitions.add(new Word("Cat","A small domesticated carnivore, Felis domestica or F. catus, bred in a number of varieties."));
            wordsanddefinitions.add(new Word("Dog","A domesticated canid, Canis familiaris, bred in many varieties."));
            wordsanddefinitions.add(new Word("Eddy","A current at variance with the main current in a stream of liquid or gas, especially one having a rotary or whirling motion."));
            wordsanddefinitions.add(new Word("Eddy","A small whirlpool."));
            wordsanddefinitions.add(new Word("Eddy","Any similar current, as of air, dust, or fog."));
            wordsanddefinitions.add(new Word("Eddy","A current or trend, as of opinion or events, running counter to the main current."));
            wordsanddefinitions.add(new Word("Orange","A colour bewteen Red and Yellow."));
            wordsanddefinitions.add(new Word("Orange","a globose, reddish-yellow, bitter or sweet, edible citrus fruit."));
            wordsanddefinitions.add(new Word("Orange","any white-flowered, evergreen citrus trees of the genus Citrus, bearing this fruit, " +
                    "as C. aurantium (bitter orange, Seville orange, or sour orange) " +
                    "and C. sinensis (sweet orange), cultivated in warm countries."));
            wordsanddefinitions.add(new Word("Orange","Any of several other citrus trees, as the trifoliate orange."));
        }
        Log.d("STAGE1","Starting to build the Standard (non-encrypted) DB with " + String.valueOf(wordsanddefinitions.size()) + " definitions");
        mDBS.getWritableDatabase().beginTransaction();
        for (Word w: wordsanddefinitions ) {
            mDBS.insertWord(w);
        }
        mDBS.getWritableDatabase().setTransactionSuccessful();
        mDBS.getWritableDatabase().endTransaction();

        Log.d("STAGE2","Starting to build the Encrypted DB with " + String.valueOf(wordsanddefinitions.size()) + " definitions");
        mDBE.getWritableDatabase().beginTransaction();
        for (Word w: wordsanddefinitions) {
            mDBE.insertWord(w);
        }

        // Decrypt the encrypted table as a TEMPORARY table
        Log.d("STAGE 3","Bulding the temporary unencrypted table");
        mDBE.buildDecrypted(true); // Build with index on word column
        mDBE.getWritableDatabase().setTransactionSuccessful();
        mDBE.getWritableDatabase().endTransaction();

        // Database now usable
        Log.d("STAGE4","Extracting data (all words that include ap in the word) from the Standard DB");
        List<Word> extracted_s = mDBS.getWords("ap",DBConstants.FILTEROPTION_ANYWHERE,10);
        for (Word w: extracted_s) {
            Log.d("WORD_STANDARD",w.getWord() + " " + w.getDefinition());
        }

        Log.d("STAGE5","Extracting data (all words that include ap in the word) from the Encrypted DB");
        List<Word> extracted_e = mDBE.getWords("ap",DBConstants.FILTEROPTION_ANYWHERE,10);
        for (Word w: extracted_e) {
            Log.d("WORD_ENCRYPTED",w.getWord() + " " + w.getDefinition());
        }

        Log.d("STAGE5","Extracting demo data from standard and from encrypted without decryption");
        Cursor csr = mDBE.getWritableDatabase().query(DBConstants.MainTable.TBLNAME,null,null,null,null,null,null,"10");
        DatabaseUtils.dumpCursor(csr);
        csr = mDBS.getWritableDatabase().query(DBConstants.MainTable.TBLNAME,null,null,null,null,null,null,"10");
        DatabaseUtils.dumpCursor(csr);
        mDBS.close();
        mDBE.close();
    }
}

Это: -

  1. (стадия 1) Создает ArrayList из Word объекты, основанные на 14 основных определениях Word, повторяемые 20000 раз (т.е. 280000 объектов).
  2. (Этап 1) Использует метод вставки для создания незашифрованной базы данных.
  3. (Этап 2) Использует метод вставки (который шифрует данные) для зашифрованной базы данных.

  4. (Этап 3) Создает временную расшифрованную таблицу, а также индекс (индекс может быть пропущен с помощью mDBE.buildDecrypted (false); (похоже, он не имеет большого количества изображений, тем более что он построен после вставок) ).

  5. (Этап 4) извлекает некоторые данные с использованием метода getWords (отфильтрованные) из незашифрованных данных и записывает извлеченные данные в журнал.

  6. (этап 5) извлекает некоторые данные с использованием метода getWords (фильтруется) из зашифрованной базы данных (из дешифрованной временной таблицы) и записывает извлеченные данные в журнал.

    • Выходные данные со Стадии 4 и Стадии 5 должны совпадать.
  7. Извлекает первые 10 строк (2 показаны для краткости) из постоянной таблицы зашифрованной базы данных (то есть зашифрованных данных, а не расшифрованных данных).

  8. Извлекает первые 10 строк (2 показаны для berevity) из незашифрованной базы данных и выгружает курсор в журнал.

    • Вывод значений 7 и 8 показывает, что находится в постоянной базе данных.

Результат

6-05 13:51:36.932  D/STAGE1: Starting to build the Standard (non-encrypted) DB with 280000 definitions
06-05 13:51:59.274 D/STAGE2: Starting to build the Encrypted DB with 280000 definitions
06-05 13:52:45.327 D/STAGE 3: Bulding the temporary unencrypted table
06-05 13:52:45.350 W/CursorWindow: Window is full: requested allocation 111 bytes, free space 98 bytes, window size 2097152 bytes
    .........
06-05 13:53:35.024 D/STAGE4: Extracting data (all words that include ap in the word) from the Standard DB
06-05 13:53:35.346 D/WORD_STANDARD: Apple Something that falls on peoples heads that causes them to discover gravity.
    ..........
06-05 13:53:35.346 D/STAGE5: Extracting data (all words that include ap in the word) from the Encrypted DB
06-05 13:53:35.346 D/WORD_ENCRYPTED: Apple Something that falls on peoples heads that causes them to discover gravity.
    ..........
06-05 13:53:35.347 D/STAGE5: Extracting demo data from standard and from encrypted without decryption
06-05 13:53:35.347 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@d05c965
06-05 13:53:35.347 I/System.out: 0 {
06-05 13:53:35.347 I/System.out:    _id=1
06-05 13:53:35.347 I/System.out:    _word=3mqQlZl55WNjeZhALFQU7w==
06-05 13:53:35.347 I/System.out:    _definition=s9Waa2HLUS2fy8q1uC9/MEKogmImu6m9MIpi9wasD9D3Zom6+/u40DnFfP6zXOyI8IgnQOKcWfQ8
06-05 13:53:35.347 I/System.out: G3uJN9a/YHMoQdEQMDMEEdSE2kWyJrc=
06-05 13:53:35.347 I/System.out: }
06-05 13:53:35.347 I/System.out: 1 {
06-05 13:53:35.347 I/System.out:    _id=2
06-05 13:53:35.347 I/System.out:    _word=LtLlycoBd9fm3eYF9aoItg==
06-05 13:53:35.347 I/System.out:    _definition=B1XJJm0eC8wPi3xGg4XgJtvIS3xL7bjixNhVAVq1UwQ=
06-05 13:53:35.347 I/System.out: }

06-05 13:53:35.348 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@7f1b63a
06-05 13:53:35.348 I/System.out: 0 {
06-05 13:53:35.348 I/System.out:    _id=1
06-05 13:53:35.348 I/System.out:    _word=Apple
06-05 13:53:35.348 I/System.out:    _definition=Something that falls on peoples heads that causes them to discover gravity.
06-05 13:53:35.348 I/System.out: }
06-05 13:53:35.348 I/System.out: 1 {
06-05 13:53:35.348 I/System.out:    _id=2
06-05 13:53:35.348 I/System.out:    _word=Bachelor
06-05 13:53:35.348 I/System.out:    _definition=An unmarried man.
06-05 13:53:35.348 I/System.out: }
06-05 13:53:35.349 I/System.out: <<<<<
0 голосов
/ 04 июня 2019

Вы пытались использовать ВАКУУМ ?

Команда VACUUM перестраивает всю базу данных. Существует несколько причин, по которым приложение может сделать это:

  • Если SQLite не работает в режиме "auto_vacuum = FULL", когда большой объем данных удаляется из файла базы данных, который он оставляет пустое пространство или «свободные» страницы базы данных. Это означает, что файл базы данных может быть больше, чем строго необходимо. Запуск VACUUM для восстановления база данных освобождает это пространство и уменьшает размер базы данных файл.

  • Частые вставки, обновления и удаления могут вызвать базу данных файл становится фрагментированным - где данные для одной таблицы или индекса разбросаны по всему файлу базы данных. Запуск VACUUM гарантирует, что каждый таблица и индекс в основном хранятся в базе данных непрерывно файл. В некоторых случаях ВАКУУМ может также уменьшить количество частично заполненные страницы в базе данных, уменьшение размера файла базы данных далее ...

Команда VACUUM работает путем копирования содержимого базы данных во временный файл базы данных, а затем перезаписать оригинал с содержимым временного файла. При перезаписи Оригинал, журнал отката или WAL-файл с опережением записи используется только как это было бы для любой другой транзакции базы данных. Это означает, что при ВАКУУМИРОВАНИИ базы данных, вдвое больше оригинала Требуется файл базы данных в свободном дисковом пространстве ...

Редактировать

Еще одна вещь, не забывайте использовать правильный тип данных, например, поле TEXT требует больше места, чем поле INTEGER.

...