发布网友 发布时间:2022-04-30 21:22
共6个回答
懂视网 时间:2022-05-01 01:43
- (BOOL)open { if (_db) { return YES; } int err = sqlite3_open([self sqlitePath], (sqlite3**)&_db ); if(err != SQLITE_OK) { NSLog(@"error opening!: %d", err); return NO; } // 若_maxBusyRetryTimeInterval大于0,那么就调用setMaxBusyRetryTimeInterval:函数 // setMaxBusyRetryTimeInterval:函数主要是调用sqlite3_busy_handler来处理其他线程已经在操作数据库的情况,默认_maxBusyRetryTimeInterval为2。
// 具体该参数有什么用,下面在FMDBDatabaseBusyHandler函数中会详解。 if (_maxBusyRetryTimeInterval > 0.0) { [self setMaxBusyRetryTimeInterval:_maxBusyRetryTimeInterval]; } return YES; } - (void)setMaxBusyRetryTimeInterval:(NSTimeInterval)timeout { _maxBusyRetryTimeInterval = timeout; if (!_db) { return; } // 处理的handler设置为FMDBDatabaseBusyHandler这个函数 if (timeout > 0) { sqlite3_busy_handler(_db, &FMDBDatabaseBusyHandler, (__bridge void *)(self)); } else { // 不使用任何busy handler处理 sqlite3_busy_handler(_db, nil, nil); } }
这里需要提一下sqlite3_busy_handler这个函数:
int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*);
第一个参数是告知哪个数据库需要设置busy handler。
第二个参数是其实就是回调函数(busy handler)了,当你调用该回调函数时,需传递给它的一个void*的参数的拷贝,也即sqlite3_busy_handler的第三个参数;另一个需要传给回调函数的int参数是表示这次锁事件,该回调函数被调用的次数。如果回调函数返回0时,将不再尝试再次访问数据库而返回SQLITE_BUSY或者SQLITE_IOERR_BLOCKED。如果回调函数返回非0, 将会不断尝试操作数据库。
总结:程序运行过程中,如果有其他进程或者线程在读写数据库,那么sqlite3_busy_handler会不断调用回调函数,直到其他进程或者线程释放锁。获得锁之后,不会再调用回调函数,从而向下执行,进行数据库操作。该函数是在获取不到锁的时候,以执行回调函数的次数来进行延迟,等待其他进程或者线程操作数据库结束,从而获得锁操作数据库。
大家也看出来了,sqlite3_busy_handler函数的关键就是这个回调函数了,此处作者定义的是一个名叫FMDBDatabaseBusyHandler的函数作为其busy handler。
// 注意:appledoc(生成文档的软件)中,对于有具体实现的C函数,比如下面这个函数, // 是有bug的。所以你在生成文档时,忽略.m文件。 // 该函数就是简单调用sqlite3_sleep来挂起进程 static int FMDBDatabaseBusyHandler(void *f, int count) { FMDatabase *self = (__bridge FMDatabase*)f; // 如果count为0,表示的第一次执行回调函数 // 初始化self->_startBusyRetryTime,供后面计算delta使用 if (count == 0) { self->_startBusyRetryTime = [NSDate timeIntervalSinceReferenceDate]; return 1; } // 使用delta变量控制执行回调函数的次数,每次挂起50~100ms // 所以maxBusyRetryTimeInterval的作用就在这体现出来了 // 当挂起的时长大于maxBusyRetryTimeInterval,就返回0,并停止执行该回调函数了 NSTimeInterval delta = [NSDate timeIntervalSinceReferenceDate] - (self->_startBusyRetryTime); if (delta < [self maxBusyRetryTimeInterval]) { // 使用sqlite3_sleep每次当前线程挂起50~100ms int requestedSleepInMillseconds = (int) arc4random_uniform(50) + 50; int actualSleepInMilliseconds = sqlite3_sleep(requestedSleepInMillseconds); // 如果实际挂起的时长与想要挂起的时长不一致,可能是因为构建SQLite时没将HAVE_USLEEP置为1 if (actualSleepInMilliseconds != requestedSleepInMillseconds) { NSLog(@"WARNING: Requested sleep of %i milliseconds, but SQLite returned %i. Maybe SQLite wasn‘t built with HAVE_USLEEP=1?", requestedSleepInMillseconds, actualSleepInMilliseconds); } return 1; } return 0; }
为什么不讲 - [FMDatabase executeQuery:]?因为- [FMDatabase executeQuery:]等等类似的函数,最终都是对- [FMDatabase executeQuery:withArgumentsInArray:orDictionary:orVAList:]的简单封装。该函数比较关键,主要是针对查询的sql语句。
- (FMResultSet *)executeQuery:(NSString *)sql withArgumentsInArray:(NSArray*)arrayArgs orDictionary:(NSDictionary *)dictionaryArgs orVAList:(va_list)args { // 判断当前是否存在数据库以供操作 if (![self databaseExists]) { return 0x00; } // 如果当前线程已经在使用数据库了,那就输出正在使用的警告 if (_isExecutingStatement) { [self warnInUse]; return 0x00; } _isExecutingStatement = YES; int rc = 0x00; sqlite3_stmt *pStmt = 0x00; // sqlite的prepared语句类型 FMStatement *statement = 0x00; // 对sqlite3_stmt的简单封装,在实际应用中,你不应直接操作FMStatement对象 FMResultSet *rs = 0x00; // FMResultSet对象是用来获取最终查询结果的 // 需要追踪sql执行状态的话,输出执行状态 if (_traceExecution && sql) { NSLog(@"%@ executeQuery: %@", self, sql); } // 调用sql语句之前,首先要将sql字符串预处理一下,转化为SQLite可用的prepared语句(预处理语句) // 使用sqlite3_prepare_v2来生成sql对应的prepare语句(即pStmt)代价很大 // 所以建议使用缓存机制来减少对sqlite3_prepare_v2的使用 if (_shouldCacheStatements) { // 获取到缓存中的prepared语句 statement = [self cachedStatementForQuery:sql]; pStmt = statement ? [statement statement] : 0x00; // prepared语句可以被重置(调用sqlite3_reset函数),然后可以重新绑定参数以便重新执行。 [statement reset]; } // 如果缓存中没有sql对应的prepared语句,那么只能使用sqlite3_prepare_v2函数进行预处理 if (!pStmt) { rc = sqlite3_prepare_v2(_db, [sql UTF8String], -1, &pStmt, 0); // 如果生成prepared语句出错,那么就根据是否需要打印错误信息(_logsErrors)以及是否遇到错误直接中止程序执行(_crashOnErrors)来执行出错处理。 // 最后调用sqlite3_finalize函数释放所有的内部资源和sqlite3_stmt数据结构,有效删除prepared语句。 if (SQLITE_OK != rc) { if (_logsErrors) { NSLog(@"DB Error: %d "%@"", [self lastErrorCode], [self lastErrorMessage]); NSLog(@"DB Query: %@", sql); NSLog(@"DB Path: %@", _databasePath); } if (_crashOnErrors) { NSAssert(false, @"DB Error: %d "%@"", [self lastErrorCode], [self lastErrorMessage]); // abort()函数表示中止程序执行,直接从调用的地方跳出。 abort(); } sqlite3_finalize(pStmt); _isExecutingStatement = NO; return nil; } } id obj; int idx = 0; // 获取到pStmt中需要绑定的参数个数 int queryCount = sqlite3_bind_parameter_count(pStmt); // pointed out by Dominic Yu (thanks!) // 举一个使用dictionaryArgs的例子/**
NSMutableDictionary*dictionaryArgs = [NSMutableDictionary dictionary]; [dictionaryArgs setObject:@"Text1" forKey:@"a"]; [db executeQuery:@"select * from namedparamcounttest where a = :a" withParameterDictionary:dictionaryArgs]; // 注意类似:AAA前面有冒号的就是参数 // 其他的参数形式如:"?", "?NNN", ":AAA", "$AAA", 或 "@AAA" */
if (dictionaryArgs) { for (NSString *dictionaryKey in [dictionaryArgs allKeys]) { // 在每个dictionaryKey之前加上冒号,比如上面的a -> :a,方便获取参数在prepared语句中的索引 NSString *parameterName = [[NSString alloc] initWithFormat:@":%@", dictionaryKey]; // 查看执行状况 if (_traceExecution) { NSLog(@"%@ = %@", parameterName, [dictionaryArgs objectForKey:dictionaryKey]); } // 在prepared语句中查找对应parameterName的参数索引值namedIdx int namedIdx = sqlite3_bind_parameter_index(pStmt, [parameterName UTF8String]); FMDBRelease(parameterName); // 可以利用索引namedIdx获取对应参数,再使用bindObject:函数将dictionaryArgs保存的value绑定给对应参数 if (namedIdx > 0) { [self bindObject:[dictionaryArgs objectForKey:dictionaryKey] toColumn:namedIdx inStatement:pStmt]; // 使用这个idx来判断sql中的所有参数值是否都绑定上了 idx++; } else { NSLog(@"Could not find index for %@", dictionaryKey); } } } else { while (idx < queryCount) { // 使用arrayArgs的例子 /** [db executeQuery:@"insert into testOneHundredTwelvePointTwo values (?, ?)" withArgumentsInArray:[NSArray arrayWithObjects:@"one", [NSNumber numberWithInteger:2], nil]]; */ if (arrayArgs && idx < (int)[arrayArgs count]) { obj = [arrayArgs objectAtIndex:(NSUInteger)idx]; } // 使用args的例子,使用args其实就是调用- (FMResultSet *)executeQuery:(NSString*)sql, ...; /** FMResultSet *rs = [db executeQuery:@"select rowid,* from test where a = ?", @"hi‘"]; */ else if (args) { obj = va_arg(args, id); } else { break; } if (_traceExecution) { if ([obj isKindOfClass:[NSData class]]) { NSLog(@"data: %ld bytes", (unsigned long)[(NSData*)obj length]); } else { NSLog(@"obj: %@", obj); } } idx++; // 绑定参数值 [self bindObject:obj toColumn:idx inStatement:pStmt]; } } // 如果绑定的参数数目不对,认为出错,并释放资源 if (idx != queryCount) { NSLog(@"Error: the bind count is not correct for the # of variables (executeQuery)"); sqlite3_finalize(pStmt); _isExecutingStatement = NO; return nil; } FMDBRetain(statement); // to balance the release below // statement不为空,进行缓存 if (!statement) { statement = [[FMStatement alloc] init]; [statement setStatement:pStmt]; // 使用sql作为key来缓存statement(即sql对应的prepare语句) if (_shouldCacheStatements && sql) { [self setCachedStatement:statement forQuery:sql]; } } // 根据statement和self(FMDatabase对象)构建一个FMResultSet对象,此函数中仅仅是构建该对象,还没使用next等函数获取查询结果 // 注意FMResultSet中含有以下成员(除了最后一个,其他成员均在此处初始化过了) /** @interface FMResultSet : NSObject { FMDatabase *_parentDB; // 表示该对象查询的数据库,主要是为了能在FMResultSet自己的函数中索引到正在操作的FMDatabase对象 FMStatement *_statement; // prepared语句 NSString *_query; // 对应的sql查询语句 NSMutableDictionary *_columnNameToIndexMap; } */ rs = [FMResultSet resultSetWithStatement:statement usingParentDatabase:self]; [rs setQuery:sql]; // 将此时的FMResultSet对象添加_openResultSets,主要是为了调试 NSValue *openResultSet = [NSValue valueWithNonretainedObject:rs]; [_openResultSets addObject:openResultSet]; // 并设置statement的使用数目useCount加1,暂时不清楚此成员有何作用,感觉也是用于调试 [statement setUseCount:[statement useCount] + 1]; FMDBRelease(statement); // 生成statement的操作已经结束 _isExecutingStatement = NO; return rs; }
- [FMResultSet next]函数其实就是对nextWithError:的简单封装。作用就是从我们上一步open中获取到的FMResultSet对象中读取查询后结果的每一行,交给用户自己处理。读取每一行的方法(即next)其实就是封装了sqlite3_step函数。而nextWithError:主要封装了对sqlite3_step函数返回结果的处理。
int sqlite3_step(sqlite3_stmt*);
sqlite3_prepare函数将SQL命令字符串解析并转换为一系列的命令字节码,这些字节码最终被传送到SQlite3的虚拟数据库引擎(VDBE: Virtual Database Engine)中执行,完成这项工作的是sqlite3_step函数。比如一个SELECT查询操作,sqlite3_step函数的每次调用都会返回结果集中的其中一行,直到再没有有效数据行了。每次调用sqlite3_step函数如果返回SQLITE_ROW,代表获得了有效数据行,可以通过sqlite3_column函数提取某列的值。如果调用sqlite3_step函数返回SQLITE_DONE,则代表prepared语句已经执行到终点了,没有有效数据了。很多命令第一次调用sqlite3_step函数就会返回SQLITE_DONE,因为这些SQL命令不会返回数据。对于INSERT,UPDATE,DELETE命令,会返回它们所修改的行号——一个单行单列的值。
// 返回YES表示从数据库中获取到了下一行数据 - (BOOL)nextWithError:(NSError **)outErr { // 尝试步进到下一行 int rc = sqlite3_step([_statement statement]); // 对返回结果rc进行处理 /** SQLITE_BUSY 数据库文件有锁 SQLITE_LOCKED 数据库中的某张表有锁 SQLITE_DONE sqlite3_step()执行完毕 SQLITE_ROW sqlite3_step()获取到下一行数据 SQLITE_ERROR 一般用于没有特别指定错误码的错误,就是说函数在执行过程中发生了错误,但无法知道错误发生的原因。 SQLITE_MISUSE 没有正确使用SQLite接口,比如一条语句在sqlite3_step函数执行之后,没有被重置之前,再次给其绑定参数,这时bind函数就会返回SQLITE_MISUSE。 */ if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { NSLog(@"%s:%d Database busy (%@)", __FUNCTION__, __LINE__, [_parentDB databasePath]); NSLog(@"Database busy"); if (outErr) { // lastError使用sqlite3_errcode获取到错误码,封装成NSError对象返回 *outErr = [_parentDB lastError]; } } else if (SQLITE_DONE == rc || SQLITE_ROW == rc) { // all is well, let‘s return. } else if (SQLITE_ERROR == rc) { // sqliteHandle就是获取到对应FMDatabase对象,然后使用sqlite3_errmsg来获取错误码的字符串 NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); if (outErr) { *outErr = [_parentDB lastError]; } } else if (SQLITE_MISUSE == rc) { // uh oh. NSLog(@"Error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); if (outErr) { if (_parentDB) { *outErr = [_parentDB lastError]; } else { // 如果next和nextWithError函数是在当前的FMResultSet关闭之后调用的 // 这时输出的错误信息应该是parentDB不存在 NSDictionary* errorMessage = [NSDictionary dictionaryWithObject:@"parentDB does not exist" forKey:NSLocalizedDescriptionKey]; *outErr = [NSError errorWithDomain:@"FMDatabase" code:SQLITE_MISUSE userInfo:errorMessage]; } } } else { // wtf? NSLog(@"Unknown error calling sqlite3_step (%d: %s) rs", rc, sqlite3_errmsg([_parentDB sqliteHandle])); if (outErr) { *outErr = [_parentDB lastError]; } } // 如果不是读取下一行数据,那么就关闭数据库 if (rc != SQLITE_ROW) { [self close]; } return (rc == SQLITE_ROW); }
与open函数成对调用。主要还是封装了sqlite_close函数。
- (BOOL)close { // 清除缓存的prepared语句,下面会详解 [self clearCachedStatements]; // 关闭所有打开的FMResultSet对象,目前看来这个_openResultSets大概也是用来调试的 [self closeOpenResultSets]; if (!_db) { return YES; } int rc; BOOL retry; BOOL triedFinalizingOpenStatements = NO; do { retry = NO;
// 调用sqlite3_close来尝试关闭数据库 rc = sqlite3_close(_db);//如果当前数据库上锁,那么就先尝试重新关闭(置retry为YES) // 同时还尝试释放数据库中的prepared语句资源
if (SQLITE_BUSY == rc || SQLITE_LOCKED == rc) { if (!triedFinalizingOpenStatements) { triedFinalizingOpenStatements = YES; sqlite3_stmt *pStmt;// sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt
)表示从数据库pDb中对应的pStmt语句开始一个个往下找出相应prepared语句,如果pStmt为nil,那么就从pDb的第一个prepared语句开始。
// 此处迭代找到数据库中所有prepared语句,释放其资源。 while ((pStmt = sqlite3_next_stmt(_db, nil)) !=0) { NSLog(@"Closing leaked statement"); sqlite3_finalize(pStmt); retry = YES; } } }
// 关闭出错,输出错误码 else if (SQLITE_OK != rc) { NSLog(@"error closing!: %d", rc); } } while (retry); _db = nil; return YES; } // _cachedStatements是用来缓存prepared语句的,所以清空_cachedStatements就是将每个缓存的prepared语句释放
// 具体实现就是使用下面那个close函数,close函数中调用了sqlite_finalize函数释放资源 - (void)clearCachedStatements { for (NSMutableSet *statements in [_cachedStatements objectEnumerator]) { // makeObjectsPerformSelector会并发执行同一件事,所以效率比for循环一个个执行要快很多 [statements makeObjectsPerformSelector:@selector(close)]; } [_cachedStatements removeAllObjects]; } // 注意:此为FMResultSet的close函数 - (void)close { if (_statement) { sqlite3_finalize(_statement); _statement = 0x00; } _inUse = NO; }// 清除_openResultSets
- (void)closeOpenResultSets { //Copy the set so we don‘t get mutation errors NSSet *openSetCopy = FMDBReturnAutoreleased([_openResultSets copy]);
// 迭代关闭_openResultSets中的FMResultSet对象 for (NSValue *rsInWrappedInATastyValueMeal in openSetCopy) { FMResultSet *rs = (FMResultSet *)[rsInWrappedInATastyValueMeal pointerValue]; // 清除FMResultSet的操作 [rs setParentDB:nil]; [rs close]; [_openResultSets removeObject:rsInWrappedInATastyValueMeal]; } }
本文结合一个基本的FMDB使用案例,介绍了FMDB基本的运作流程和内部实现。总的来说,FMDB就是对SQLite的封装,所以学习FMDB根本还是在学习SQLite数据库操作。
SQLITE和多线程
sqlite3之sqlite3_busy_handler使用详解
SQlite数据库的C编程接口(三) 预处理语句(Prepared Statements) ——《Using SQlite》读书笔记 .
SQlite数据库的C编程接口(六) 返回值和错误码(Result Codes and Error Codes) ——《Using SQlite》读书笔记
【原】FMDB源码阅读(一)
标签:
热心网友 时间:2022-04-30 22:51
原因是:操作错误导致的,具体解决方法步骤如下:
1.首先打开电脑,在电脑中运行“易语言”主程序,弹出“新建项目对话框”,选择“Windows窗口程序”,点击“确定”,进入“Windows窗口编程界面”。
2.接下来,拖放一个标签组件、两个编辑框组件、一个按钮组件和一个分组框组件。将它们放置在适当的位置,并将窗口的宽度和高度设置为适当的大小。确保窗户简洁美观。
3.界面调整后,将窗口标题、标签标题、框标题和按钮标题分组到相应内容中,并根据规范对编辑框和按钮组件进行命名。准备编写程序代码。
4.下一步是编写代码,在“_button_source_wasclicked”事件子例程中,编写如下代码[editbox_websource]。内容=文本(HTTP读取文件(编辑框_url)。内容)。
5.代码正确编写后,进入调试阶段。按“F5”快捷键运行程序,我们以百度体验网站为例,点击“source”按钮,等待程序执行。
6.从web源代码编辑框中获得反馈结果分析,如果源代码出现凌乱的代码通常是一个编码问题。此时,您需要进行代码转换,您可以看到正常的代码。然后,您需要对代码做一些小的修改。
7、重测程序,从web源编辑框反馈的结果可以看出web源的阅读是正确的。
热心网友 时间:2022-05-01 00:09
易语言 http读文件() 读出来的网页源码是乱码的原因是操作错误导致的,具体解决方法步骤如下:
1、首先打开计算机,在计算机内运行“易语言”主程序,弹出“新建工程对话框”选择“Windows窗口程序”然后点击“确定”按钮,进入“Windows窗口程序设计界面”。
2、接下来,拖放标签组件一个、编辑框组件两个、按钮组件一个及分组框组件一个。将它们放置到合适的位置并将窗口的宽高设置到适当的大小。以确保窗口的简洁美观。
3、待界面调整完毕之后,将窗口标题、标签标题、分组框标题及按钮标题修改成相应内容,并且将编辑框、按钮组件规范命名。为编写程序代码做准备。
4、接下来就是编写代码了,在“_按钮_获取源码_被单击”事件子程序下,编写如下代码【 编辑框_网页源码.内容 = 到文本 (HTTP读文件 (编辑框_网址.内容)) 】。
5、待代码编写无误后,进入调试阶段。按下“F5”快捷键运行程序,网址我们以百度经验的网址为例,然后点击“获取源码”按钮,等待程序执行。
6、从网页源码编辑框得到的反馈结果分析,如果源码中出现乱码通常是编码方式的问题。这个时候,需要进行转码,就能看到正常的代码了。那么,代码也需要做小的改动。
7、重新测试程序,从网页源码编辑框的反馈结果可以看出,这次读取的网页源码是正确的。
热心网友 时间:2022-05-01 01:44
这时因为http读文件返回的是gb2312编码数据,而网页中使用的是uft-8编码,可以使用精易模块中的url编码命令将返回的数据进行解码。
1、新建易语言程序,在窗口中添加按钮,然后设置按钮的标题,双击按钮添加按钮点击事件:
2、使用http读文件命令读取网页,这里以读取百度首页为例,然后将返回的数据输出调试出来,可以发现这时读取的源码是乱码:
3、点击窗口左侧的“程序”按钮,然后将精易模块引入到易语言中:
4、使用“编码_URL解码”命令对返回的数据进行解码,这个命令的第二个参数填写为真则表示返回的数据是uft-8编码格式,这时返回的数据就被解码到中文了:
热心网友 时间:2022-05-01 03:35
你要进行编码转换的呀。。。
我写的源码
=====================
.版本 2
.支持库 internet
.支持库 iconv
.支持库 spec
.子程序 _按钮1_被单击
.局部变量 字节集, 字节集
.局部变量 转换句柄, 整数型
字节集 = HTTP读文件 (“http://www.baidu.com/”)
转换句柄 = 编码转换_打开 (#编码_UTF_8, #编码_GBK)
字节集 = 编码转换_转换 (转换句柄, 字节集, )
编码转换_关闭(转换句柄)
调试输出 (到文本 (字节集))
-----------------以下是百度主页的源码(转换后)
---------------------------你刚才的地址
热心网友 时间:2022-05-01 05:43
你需要转码 中文字符才能显示追问转码要自己编写代码吗追答.版本 2
.子程序 彗星URL解码_UTF8, 文本型, 公开, 返回已解码的URL
.参数 Bin_URL, 文本型, , 欲解码的文本
.局部变量 循环容器, 整数型
.局部变量 Len, 整数型
.局部变量 HEX, 文本型
.局部变量 目标, 文本型
.局部变量 H, 整数型
.局部变量 L, 整数型
Len = 取文本长度 (Bin_URL)
循环容器 = 1
HEX = “0123456789ABCDEF”
.判断循环首 (循环容器 ≤ Len)
.判断开始 (取文本中间 (Bin_URL, 循环容器, 1) ≠ “%”)
目标 = 目标 + 取文本中间 (Bin_URL, 循环容器, 1)
.默认
循环容器 = 循环容器 + 1
H = 寻找文本 (HEX, 到大写 (取文本中间 (Bin_URL, 循环容器, 1)), , 假) - 1
循环容器 = 循环容器 + 1
L = 寻找文本 (HEX, 到大写 (取文本中间 (Bin_URL, 循环容器, 1)), , 假) - 1
目标 = 目标 + 字符 (H × 16 + L)
.判断结束
循环容器 = 循环容器 + 1
.判断循环尾 ()
返回 (Utf8转ansi (到字节集 (目标)))