Android数据库代码优化(2) - 从SQLite说起

从SQLite说起

如果没有SQLite的基础,我们只是从Android封装的SQLite API去学习的话,难免思路会受到限制。所以,我们还是需要老老实实从头开始学习SQLite.
当我们有一身的SQLite武功之后,再去看Android的封装,就能更清楚如何发挥SQLite的特长。

SQLite的核心只有一个c文件,访问的db也存在一个文件当中。所以,我们完全可以把它嵌入到另外一个程序中。

在mac上,可以通过Homebrew来安装。安装之后,我们就可以用sqlite3的API来写代码了。

先来个能编过的sqlite3调用例子吧

我们找个网上找到的最简单的打开关闭SQLite数据库的例子:

#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    sqlite3 *db;
    char *zErrMsg = 0;
    int rc;

    rc = sqlite3_open("contacts.db", &db);

    if (rc)
    {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        exit(0);
    }
    else
    {
        fprintf(stderr, "Opened database successfully\n");
    }
    sqlite3_close(db);
}

我们先不管它是什么意思,先编译一下试试:

gcc -o test_sqlite test.c -lsqlite3

然后运行一下吧,需要本地有个叫contacts.db的数据库。

./test_sqlite

输出为:

Opened database successfully

从上面的例子,我们可以学习到两个容易理解的API: sqlite3_open和sqlite3_close.

SQLite3是一个基于VDBE的数据库引擎

有了能运行的环境之后,我们就来看看SQLite数据库引擎的结构吧:

sqlite structure

从这张官方图上,我们可以看到,除了工具和测试代码之外,SQLite的核心部分分为三部分:核心,编译器和后端。

核心部分就是对SQL命令的处理的部分,它通过编译器来编译成VDBE(Virtual Database Engine)能执行的代码。
后端是真正对数据库进行操作的部分,包括B-树的查找结构等。

喜欢划重点的同学注意啦,重点来了:调用SQLite3数据库的代码优化的第一个点就是将编译好的字节码保存起来,下次用的时候直接调用。
这么重要的功能,SQLite3 API中当然有提供,这就是后面我们会大量学习使用的sqlite3_prepare和sqlite3_prepare_v2函数。
Android对此也有同样的封装,提供了SQLiteStatement来实现预编译代码的保存。

有同学问了,我的SQL语句并不是一成不变的,语句中的参数经常改变,这样的话,编译出来的代码就没有用了啊?
这在SQLite3的设计中当然是有考虑到的,编译好的语句,是可以支持参数的。我们首先使用sqlite3_prepare_v2编译,然后再通过sqlite3_bind_函数来绑定参数。下次如果换了参数,先调用sqlite3_reset清除掉绑定信息,然后再重新用sqlite3_bind_来做绑定新参数,就可以了。

一个调用sqlite3实现数据库操作的功能可以用下面的步骤来套用:

  1. 根据业务需求,构造sql语句
  2. 调用sqlite3_prepare_v2函数来编译sql语句
  3. 如果有参数,调用sqlite3_bind_*函数来绑定参数
  4. 调用sqlite3_step函数来执行一次sql操作,直至所有操作都完成
  5. 下次再使用第2步编译出来的语句时,调用sqlite3_reset函数清理参数。然后重复第3步的操作
  6. 最后,调用sqlite3_finalize来销毁预编译语句

下面我们直接开始实操,首先先举个select的例子。
我们以Android中联系人数据库为例,取其中的calls表的简化版:

CREATE TABLE calls (
_id INTEGER PRIMARY KEY AUTOINCREMENT,
sourceid TEXT,
number TEXT
...
);

虽然字段很多,我们就关注id和号码就好。

如何写查询语句

我们先来一半,我们选_id和number这两列,然后看看返回的数据中是不是两列:
核心代码如下:

    rc = sqlite3_prepare_v2(db, sql_select_caller, -1, &stmt, &tail);

    rc = sqlite3_step(stmt);
    int ncols = sqlite3_column_count(stmt);

    printf("The column counts of calls is:%d\n", ncols);

    sqlite3_finalize(stmt);

完整版的代码,便于大家实验:

#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    sqlite3 *db;
    char *zErrMsg = 0;
    int rc;
    const char *sql_select_caller = "select _id, number from calls";
    sqlite3_stmt *stmt;
    const char *tail;

    rc = sqlite3_open("contacts.db", &db);

    if (rc)
    {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        exit(0);
    }
    else
    {
        fprintf(stderr, "Opened database successfully\n");
    }

    //select _id, number from calls
    rc = sqlite3_prepare_v2(db, sql_select_caller, -1, &stmt, &tail);

    rc = sqlite3_step(stmt);
    int ncols = sqlite3_column_count(stmt);

    printf("The column counts of calls is:%d\n", ncols);

    sqlite3_finalize(stmt);

    sqlite3_close(db);
}

下面我们直接调用sqlite3_step去读每一条记录,增加下面一段:

    while(rc == SQLITE_ROW){
        printf("calls ID=%d,\t",sqlite3_column_int(stmt,0));
        printf("number=%s\n",sqlite3_column_text(stmt,1));
        rc = sqlite3_step(stmt);
    }

如果sqlite3_step返回的结果是SQLITE_ROW,说明这一次执行取到了一条符合条件的记录。每次取一条记录。

完整代码如下:

#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
    sqlite3 *db;
    char *zErrMsg = 0;
    int rc;
    const char *sql_select_caller = "select _id, number from calls";
    sqlite3_stmt *stmt;
    const char *tail;

    rc = sqlite3_open("contacts.db", &db);

    if (rc)
    {
        fprintf(stderr, "Can't open database: %s\n", sqlite3_errmsg(db));
        exit(0);
    }
    else
    {
        fprintf(stderr, "Opened database successfully\n");
    }

    //select _id, number from calls
    rc = sqlite3_prepare_v2(db, sql_select_caller, -1, &stmt, &tail);

    rc = sqlite3_step(stmt);
    int ncols = sqlite3_column_count(stmt);

    printf("The column counts of calls is:%d\n", ncols);

    while (rc == SQLITE_ROW)
    {
        printf("calls ID=%d,\t", sqlite3_column_int(stmt, 0));
        printf("number=%s\n", sqlite3_column_text(stmt, 1));
        rc = sqlite3_step(stmt);
    }

    sqlite3_finalize(stmt);

    sqlite3_close(db);
}

输出如下例:

The column counts of calls is:2
calls ID=1, number=18600009876
calls ID=2, number=18600019876
calls ID=3, number=18600029876
calls ID=4, number=18600039876
calls ID=5, number=18600049876
calls ID=6, number=18600059876
calls ID=7, number=18600069876
calls ID=8, number=18600079876
calls ID=9, number=18600089876
calls ID=10,    number=18600099876

如何写非查询语句

上面的例子是针对查询语句的,我们再举个非查询语句的例子。比如我们试个插入的例子。

void insert_item(sqlite3 *db)
{
    const char *sql_insert_sample = "insert or ignore into calls (_id,number) values (?1,?2);";
    sqlite3_stmt *stmt = NULL;
    const char *tail = NULL;

    int rc = sqlite3_prepare_v2(db, sql_insert_sample, -1, &stmt, &tail);

    sqlite3_bind_int(stmt, 1, 1000);
    sqlite3_bind_text(stmt, 2, "01084993677", 11, NULL);

    rc = sqlite3_step(stmt);

    if (rc == SQLITE_DONE)
    {
        printf("Last inserted row id=%ld\n", (long)sqlite3_last_insert_rowid(db));
    }
    else
    {
        printf("Insert failed!");
    }

    sqlite3_finalize(stmt);
}

输出如下:

Last inserted row id=0

小结

上面,我们就查询和非查询两种情况,学习了如何使用SQLite3的API。
剩下的工作主要就是构造SQL语句以及处理返回结果了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容