关闭 x
IT技术网
    技 采 号
    ITJS.cn - 技术改变世界
    • 实用工具
    • 菜鸟教程
    IT采购网 中国存储网 科技号 CIO智库

    IT技术网

    IT采购网
    • 首页
    • 行业资讯
    • 系统运维
      • 操作系统
        • Windows
        • Linux
        • Mac OS
      • 数据库
        • MySQL
        • Oracle
        • SQL Server
      • 网站建设
    • 人工智能
    • 半导体芯片
    • 笔记本电脑
    • 智能手机
    • 智能汽车
    • 编程语言
    IT技术网 - ITJS.CN
    首页 » 安卓开发 »Android 中 SQLite 性能优化

    Android 中 SQLite 性能优化

    2015-12-15 00:00:00 出处:InfoQ - 段建华
    分享

    数据库是应用开发中常用的技术,在Android应用中也不例外。Android默认使用了SQLite数据库,在应用程序开发中,我们使用最多的无外乎增删改查。纵使操作简单,也有可能出现查找数据缓慢,插入数据耗时等情况,假如出现了这种问题,我们就需要考虑对数据库操作进行优化了。本文将介绍一些实用的数据库优化操作,希望可以帮助大家更好地在开发过程中使用数据库。

    建立索引

    很多时候,我们都听说,想要查找快速就建立索引。这句话没错,数据表的索引类似于字典中的拼音索引或者部首索引。

    索引的解释

    重温一下我们小时候查字典的过程:

    对于已经知道拼音的字,比如中这个字,我们只需要在拼音索引里面找到zhong,就可以确定这个字在词典中的页码。 对于不知道拼音的字,比如欗这个字,我们只需要在部首索引里面查找这个字,就能找到确定这个字在词典中的页码。

    没错,索引做的事情就是这么简单,使得我们不需要查找整个数据表就可以实现快速访问。

    建立索引

    创建索引的基本语法如下

    CREATE INDEX index_name ON table_name;

    创建单列索引

    CREATE INDEX index_name ON table_name (column_name);

    索引真的好么

    毋庸置疑,索引加速了我们检索数据表的速度。然而正如西方谚语 “There are two sides of a coin”,索引亦有缺点:

    对于增加,更新和删除来说,使用了索引会变慢,比如你想要删除字典中的一个字,那么你同时也需要删除这个字在拼音索引和部首索引中的信息。 建立索引会增加数据库的大小,比如字典中的拼音索引和部首索引实际上是会增加字典的页数,让字典变厚的。 为数据量比较小的表建立索引,往往会事倍功半。

    所以使用索引需要考虑实际情况进行利弊权衡,对于查询操作量级较大,业务对要求查询要求较高的,还是推荐使用索引的。

    编译SQL语句

    SQLite想要执行操作,需要将程序中的sql语句编译成对应的SQLiteStatement,比如select * from record这一句,被执行100次就需要编译100次。对于批量处理插入或者更新的操作,我们可以使用显式编译来做到重用SQLiteStatement。

    想要做到重用SQLiteStatement也比较简单,基本如下:

    编译sql语句获得SQLiteStatement对象,参数使用 代替 在循环中对SQLiteStatement对象进行具体数据绑定,bind方法中的index从1开始,不是0

    请参考如下简单的使用代码

     private void insertWithPreCompiledStatement(SQLiteDatabase db) {
        String sql = "INSERT INTO " + TableDefine.TABLE_RECORD + "( " + TableDefine.COLUMN_INSERT_TIME + ") VALUES( )";
        SQLiteStatement  statement = db.compileStatement(sql);
        int count = 0;
        while (count < 100) {
            count++;
            statement.clearBindings();
            statement.bindLong(1, System.currentTimeMillis());
            statement.executeInsert();
        }
    }

    显式使用事务

    在Android中,无论是使用SQLiteDatabase的insert,delete等方法还是execSQL都开启了事务,来确保每一次操作都具有原子性,使得结果要么是操作之后的正确结果,要么是操作之前的结果。

    然而事务的实现是依赖于名为rollback journal文件,借助这个临时文件来完成原子操作和回滚功能。既然属于文件,就符合Unix的文件范型(Open-Read/Write- Close),因而对于批量的修改操作会出现反复打开文件读写再关闭的操作。然而好在,我们可以显式使用事务,将批量的数据库更新带来的journal文件打开关闭降低到1次。

    具体的实现代码如下:

     private void insertWithTransaction(SQLiteDatabase db) {
        int count = 0;
        ContentValues values = new ContentValues();
        try {
            db.beginTransaction();
            while (count++ < 100) {
                values.put(TableDefine.COLUMN_INSERT_TIME, System.currentTimeMillis());
                db.insert(TableDefine.TABLE_RECORD, null, values);
            }
              db.setTransactionSuccessful();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            db.endTransaction();
        }
    }

    上面的代码中,假如没有异常抛出,我们则认为事务成功,调用db.setTransactionSuccessful();确保操作真实生效。假如在此过程中出现异常,则批量数据一条也不会插入现有的表中。

    查询数据优化

    对于查询的优化,除了建立索引以外,有以下几点微优化的建议

    按需获取数据列信息

    通常情况下,我们处于自己省时省力的目的,对于查找使用类似这样的代码

     private void badQuery(SQLiteDatabase db) {
        db.query(TableDefine.TABLE_RECORD, null, null, null, null, null, null) ;
    }

    其中上面方法的第二个参数类型为String[],意思是返回结果参考的colum信息,传递null表明需要获取全部的column数据。这里建议大家传递真实需要的字符串数据对象表明需要的列信息,这样做效率会有所提升。

    提前获取列索引

    当我们需要遍历cursor时,我们通常的做法是这样

     private void badQueryWithLoop(SQLiteDatabase db) {
        Cursor cursor = db.query(TableDefine.TABLE_RECORD, new String[]{TableDefine.COLUMN_INSERT_TIME}, null, null, null, null, null) ;
        while (cursor.moveToNext()) {
            long insertTime = cursor.getLong(cursor.getColumnIndex(TableDefine.COLUMN_INSERT_TIME));
        }
    }

    但是假如我们将获取ColumnIndex的操作提到循环之外,效果会更好一些,修改后的代码如下:

     private void goodQueryWithLoop(SQLiteDatabase db) {
        Cursor cursor = db.query(TableDefine.TABLE_RECORD, new String[]{TableDefine.COLUMN_INSERT_TIME}, null, null, null, null, null) ;
        int insertTimeColumnIndex = cursor.getColumnIndex(TableDefine.COLUMN_INSERT_TIME);
        while (cursor.moveToNext()) {
            long insertTime = cursor.getLong(insertTimeColumnIndex);
        }
        cursor.close();
    }

    ContentValues的容量调整

    SQLiteDatabase提供了方便的ContentValues简化了我们处理列名与值的映射,ContentValues内部采用了 HashMap来存储Key-Value数据,ContentValues的初始容量是8,假如当添加的数据超过8之前,则会进行双倍扩容操作,因此建议对ContentValues填入的内容进行估量,设置合理的初始化容量,减少不必要的内部扩容操作。

    及时关闭Cursor

    使用数据库,比较常见的就是忘记关闭Cursor。关于如何发现未关闭的Cursor,我们可以使用StrictMode,详细请戳这里 Android性能调优利器StrictMode

    耗时异步化

    数据库的操作,属于本地IO,通常比较耗时,假如处理不好,很容易导致ANR,因此建议将这些耗时操作放入异步线程中处理,这里推荐一个单线程 + 任务队列形式处理的HandlerThread实现异步化。

    源码下载

    示例源码,存放在Github,地址为 AndroidSQLiteTuningDemo

    上一篇返回首页 下一篇

    声明: 此文观点不代表本站立场;转载务必保留本文链接;版权疑问请联系我们。

    别人在看

    抖音安全与信任开放日:揭秘推荐算法,告别单一标签依赖

    ultraedit编辑器打开文件时,总是提示是否转换为DOS格式,如何关闭?

    Cornell大神Kleinberg的经典教材《算法设计》是最好入门的算法教材

    从 Microsoft 下载中心安装 Windows 7 SP1 和 Windows Server 2008 R2 SP1 之前要执行的步骤

    Llama 2基于UCloud UK8S的创新应用

    火山引擎DataTester:如何使用A/B测试优化全域营销效果

    腾讯云、移动云继阿里云降价后宣布大幅度降价

    字节跳动数据平台论文被ICDE2023国际顶会收录,将通过火山引擎开放相关成果

    这个话题被围观超10000次,火山引擎VeDI如此解答

    误删库怎么办?火山引擎DataLeap“3招”守护数据安全

    IT头条

    平替CUDA!摩尔线程发布MUSA 4性能分析工具

    00:43

    三起案件揭开侵犯个人信息犯罪的黑灰产业链

    13:59

    百度三年开放2.1万实习岗,全力培育AI领域未来领袖

    00:36

    工信部:一季度,电信业务总量同比增长7.7%,业务收入累计完成4469亿元

    23:42

    Gartner:2024年全球半导体营收6559亿美元,AI助力英伟达首登榜首

    18:04

    技术热点

    iOS 8 中如何集成 Touch ID 功能

    windows7系统中鼠标滑轮键(中键)的快捷应用

    MySQL数据库的23个特别注意的安全事项

    Kruskal 最小生成树算法

    Ubuntu 14.10上安装新的字体图文教程

    Ubuntu14更新后无法进入系统卡在光标界面解怎么办?

      友情链接:
    • IT采购网
    • 科技号
    • 中国存储网
    • 存储网
    • 半导体联盟
    • 医疗软件网
    • 软件中国
    • ITbrand
    • 采购中国
    • CIO智库
    • 考研题库
    • 法务网
    • AI工具网
    • 电子芯片网
    • 安全库
    • 隐私保护
    • 版权申明
    • 联系我们
    IT技术网 版权所有 © 2020-2025,京ICP备14047533号-20,Power by OK设计网

    在上方输入关键词后,回车键 开始搜索。Esc键 取消该搜索窗口。