sqlite3的C语言使用(三)
估计这是sqlite系列最后一篇,sqlite的基本功能这一篇写完了就应该差不多了,不过还有更多高级功能大家可以自己去发现。上一篇
我们今天目标是写一个程序,把我们的字典文件pass.txt中的所有密码导入数据库中。我们的pass.txt中有93K多的数据行数,我们可以通过这个程序来测试sqlite的速度。
像往常一样,先建立一个sqlite3变量db,并新建一个数据库文件xxx.db,再创建一个表pass,如下:
sqlite3 * db;
char * errmsg;
int i = 0;
sqlite3_open("xxx.db",&db);
int rc = sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS pass(pass_id INTEGER PRIMARY KEY,pass_text VARCHAR(64) UNIQUE);",NULL,NULL,&errmsg);
if (SQLITE_OK != rc)
{
printf("%sn",errmsg);
return 0;
}
表中就两个字段,pass_id是主键,不管有没有作用,每个表中最好有一个主键。pass_text保存密码的明文。现在我介绍一个sql语句新的用法:使用通配符创建一个sql语句,然后用sqlite3_bind来给sql绑定数据。
比如我们现在可以定义一个sql语句:
char sql[256] = "INSERT INTO pass(pass_text) VALUES(?);";
这条语句中加入了“?”通配符,如果直接执行这个语句的话,是不会成功的。我们需要把数据绑定到这个sql语句里。这样做有什么好处呢?当我们要插入大量数据的时候,我们如果每插入每个语句都用sqlite3_prepare来准备一下sql语句的话会非常慢,所以我们用通配符先建立一个sql语句,用sqlite3_prepare来准备好。再进入循环,每次循环只需调用sqlite3_bind之类的函数给通配符赋值,再用sqlite3_step来执行即可。一个程序里面只调用一次sqlite3_prepare,大大减少了运行时间。
所以我们的程序接下来可以这么写:
sqlite3_stmt * stmt;
sqlite3_prepare(db,sql,-1,&stmt,NULL);
char szPass[256];
FILE * pf = fopen("pass.txt","r");
int nRow = CountRow(pf);
//自己写一个CountRow函数计算字典所有行数
fseek(pf,0,SEEK_SET);
//将文件流指针定位到文件开头
sqlite3_exec(db, "begin;",NULL,NULL,NULL);
//开启sqlite事务,加快插入db速度(重要)
while (fgets(szPass,256,pf) != NULL)
{
sqlite3_bind_text(stmt,1,szPass,-1,SQLITE_STATIC);
rc = sqlite3_step(stmt);
if (SQLITE_DONE != rc)
{
printf("数据%s重复或插入出错n",szPass);
printf("是否继续(1继续0退出)n");
int n;
scanf("%d",&n);
if (1 == n)
{
continue;
}
else
{
break;
}
}
sqlite3_reset(stmt);
i++;
if (i % (nRow / 100) == 0)
{
printf("已完成%02d/%dn",i,nRow);
}
}
sqlite3_exec(db,"commit;",NULL,NULL,NULL);
//关闭sqlite事务
sqlite3_finalize(stmt);
sqlite3_close(db);
fclose(pf);
printf("完成n");
return 0;
}
大家可以看到,我们的while循环第一句是sqlite3_bind_text函数,这个函数是sqlite3_bind一组函数的其中之一,作用就是将字符串类型的数据绑定到通配符?上。比如通过fgets函数我们得到的密码是123456,则绑定之后我们的sql语句变成了"INSERT INTO pass(pass_text) VALUES('123456');"。再使用sqlite3_step执行它,就可以完成一个密码的插入工作。
下面是所有sqlite3_bind函数,从函数名大概就能知道是干什么的。官方文档
int sqlite3_bind_blob(sqlite3_stmt*, int, const void*, int n, void(*)(void*)); int sqlite3_bind_double(sqlite3_stmt*, int, double); int sqlite3_bind_int(sqlite3_stmt*, int, int); int sqlite3_bind_int64(sqlite3_stmt*, int, sqlite3_int64); int sqlite3_bind_null(sqlite3_stmt*, int); int sqlite3_bind_text(sqlite3_stmt*, int, const char*, int n, void(*)(void*)); int sqlite3_bind_text16(sqlite3_stmt*, int, const void*, int, void(*)(void*)); int sqlite3_bind_value(sqlite3_stmt*, int, const sqlite3_value*); int sqlite3_bind_zeroblob(sqlite3_stmt*, int, int n); |
---|
我就说说我们用的sqlite3_bind_text各个参数的意思:第一个参数是stmt,sql语句的句柄;第二个参数是通配符的索引(也就是给第几个通配符绑定值),从1开始;第三个参数是待绑定的字符串;第四个参数是字符串长度,我填-1表示“字符串从第一个字符开始到第一个 结束”;第五个参数我填入SQLITE_STATIC,意思是“当本函数执行失败就释放通配符”。
其他的sqlite3_bind与此类似,不再赘述。
继续看循环体,我写的if语句的作用主要是防止有重复的密码出现(因为我的字段pass_text是unique约束,如果密码重复INSERT语句就会出错)。如果有重复密码出现,让用户决定是否继续运行程序。这个功能也可以去掉,重复密码出现虽然会出错,但也不会影响下一条语句的执行。
继续看循环体,if语句后面有一个sqlite3_reset(stmt);意思是重新设置stmt,每次sqlite3_step后都要重设一次,才能再次给sql语句绑定。
继续看循环体,后面又有一个if语句,作用是计算一下执行了多少语句,告诉用户。如果我们待插入的数据很多,程序运行很久,最好在每隔一段时间告诉用户执行了多少数据。这个功能去掉也可以。
循环体完了就代表程序执行结束了,再使用sqlite3_finalize释放stmt句柄,sqlite3_close释放db连接,fclose关闭pass.txt文件。这些操作都是写C程序的基本素质。
刚才介绍的时候我跳过了两条语句sqlite3_exec(db, "begin;",NULL,NULL,NULL);和sqlite3_exec(db, "commit;",NULL,NULL,NULL);这两条语句作用是开启/关闭事务。如果我们不运行begin;命令,sqlite会在执行每条操作的时候自动运行它,我们数据有9W多条,我们就要运行9W多次,速度慢上成百上千倍(大家可以试着把这两条语句注释掉再运行程序,插入900条数据就要2分钟)
我们来看看运行结果:
在我的电脑上不到两秒就运行完了,得到了一个3517KB的数据库文件xxx.db。
最后,来说说我对sqlite数据库的评价。我觉得sqlite数据库小巧方便,虽然功能上无法和mysql这种关系型数据库比较,但是速度却一点也不输给其他大型数据库。特别是在嵌入型的程序中,sqlite可以说是首选了吧。我会把我这次的工程文件(包括字典pass.txt和sqlite3的库文件sqlite3.dll、sqlite3.lib、sqlite3.h)在附件里打包给大家,用VS2010建的工程,主要的代码都在sqlite_bind_test.cpp中。
- “熊医生”出诊正确率超九成 医院:人工智能更多是辅助
- PLC编程优化方法,让程序运行提速!
- 这是硅谷狂人马斯克对未来做出的11个大胆预测,人工智能比核武器更危险
- 在腾讯云上使用自建DNS
- Spring 4.0.2 学习笔记(1) - 最基本的注入
- 关于女神SQLite的疑惑(2)
- WordPress纯代码仿无觅相关文章图文模式功能(增强版)
- 人工智能时代已悄然来临……
- 人民日报发布周鸿祎署名文章:迎接“大安全”时代的新威胁
- Mono 3.2 上跑NUnit测试
- 为WordPress 文章中的链接自动添加 nofollow标签
- 腾讯刘炽平:海外用户破7000万 微信带开发者“出海”
- 研究称性别不均衡或导致人工智能持有性别偏见
- ASP.NET 2.0加密Web.config 配置文件
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Go实现字符串相乘无溢出最详细解释
- 寻找和为定值的两个数
- 还不会命令行?用Go Flag自写命令行程序
- 【go】剑指offer:常见排序算法
- 剑指offer:重建一个二叉树
- Redis基础数据类型(string、hash、list)
- 【go】编程之法:01背包问题及滚动数组优化
- 【go】剑指offer:求一个数的整数次方
- 《编程珠玑》字符串包含
- redis基本数据类型(集合、HyperLogLog、地理位置)
- 【go】剑指offer:不同程序员遇到相同的题
- Spring全家桶之SpringSecurity
- Go实现字符串全排列字典序排列详解
- Go实现字符串全排列详解递归
- springboot整合RSA进行sign签名校验