在 iphone SQLite (FMDB) 中加载 10k+ 行

Posted

技术标签:

【中文标题】在 iphone SQLite (FMDB) 中加载 10k+ 行【英文标题】:Loading 10k+ rows in iphone SQLite (FMDB) 【发布时间】:2011-09-26 08:16:47 【问题描述】:

我正在创建一个字典应用程序,我正在尝试将术语加载到 iPhone 字典中以供使用。这些术语是从此表中定义的 (SQLite):

id -> INTEGER autoincrement PK 
termtext -> TEXT
langid -> INT
normalized -> TEXT 

使用归一化是因为我用希腊语写作,并且我在 sqlite 引擎上没有用于搜索变音符号的 icu,所以我使 termtext 变音符号/大小写不敏感。它也是主要的搜索字段,与可能是“视图”字段的 termtext 形成对比。

我已经定义了一个这样的类(比如 POJO):

terms.h

#import <Foundation/Foundation.h>
@interface Terms : NSObject 
 NSUInteger termId; // id
 NSString* termText; // termtext
 NSUInteger langId; // langid
 NSString* normalized; // normalized

@property (nonatomic, copy, readwrite) NSString* termText;
@property (nonatomic, copy, readwrite) NSString* normalized;
@property (assign, readwrite) NSUInteger termId;
@property (assign, readwrite) NSUInteger langId;
@end

terms.c

#import "Terms.h"

@implementation Term
@synthesize termId;
@synthesize termText;
@synthesize langId;
@synthesize normalized;
@end

现在在我的代码中,我使用FMDB 作为SQLite 数据库的包装器。我使用以下代码加载条款:

[... fmdb defined database as object, opened ]
NSMutableArray *termResults = [[[NSMutableArray alloc] init] autorelease];
FMResultSet *s = [database executeSQL:@"SELECT id, termtext, langid, normalized FROM terms ORDER BY normalized ASC"];
while ([s next]) 
 Term* term = [[Terms alloc] init];
 term.termId = [s intForColumn:@"id"];
 [... other definitions]
 [termResults addObject:term];
 [term release];

然后将整个 termResults 加载到 UITableView(在 viewdidload 上),但每次启动我的应用程序时加载最多需要 5 秒。有什么方法可以加快这个过程吗?我在SQLite 上索引了id、termText 和规范化。

*** 更新:添加 cellForRowAtIndexPath ****

[.. standard cell definition...]
// Configure the cell
Term* termObj = [self.termResults objectAtIndex:indexPath.row];
cell.textLabel.text = termObj.termText;
return cell;

【问题讨论】:

您应该检查一下,但我认为 while 循环正在杀死您。是否有任何理由将您的结果移动到数组中?我假设您正在使用它来填充表格视图。如果是这种情况,那么只需在数据源中使用结果集对象。只要您知道如何取出您的对象,就没有什么说您需要为此使用数组。 真的有必要让所有这些对象在运行时都存在吗?你不能按需从数据库中加载它们并缓存它们吗? 你能给我们看看 cellForRowAtIndexPath 方法定义吗? 我也在使用这个数组作为 uisearchbarcontroller 的来源(进行过滤)。在我看到的示例(苹果)中,它使用数组进行搜索操作。 DarkDust:规范之一是让所有单词在运行时可用。 UISearchBarController 也使用相同的数组进行过滤(使用 nspredicate,速度很快)。所以整个问题是我将如何实现 10k+ 行的快速加载。关闭应用后 FMDB 缓存是否仍然有效? 【参考方案1】:

由于您似乎无论如何都将整个数据库加载到内存中(并且认为这是必须的),所以使用 SQLite 的全部意义几乎没有了。

所以如果数据是静态(不改变),我会将数据库变成对象一次,然后序列化对象(实现NSCoding协议)并存储这个数组(或更好然而,字典)使用writeToFile:atomically:。拥有该文件后(在开发期间执行此操作),您可以在运行时使用 arrayWithContentsOfFile: 轻松加载它,在这种情况下应该更快。

【讨论】:

我会试试看,这似乎是个好主意!【参考方案2】:

将这么多数据加载到您的应用中需要时间。最好的方法是将数据从数据库加载到一个单独的线程到主应用程序线程。在加载每个项目时,将消息发送回主线程以将项目添加到支持表视图的数组中。这样,您的应用程序加载时间将很短,并且您的 UI 将在从数据库加载数据时做出响应。基本思路如下:

NSMutableArray *termResults = [NSMutableArray array];

// Load each item from the db on a separate thread using a block
dispatch_queue_t globalQueue = dispatch_get_global_queue();

dispatch_async(globalQueue, ^() 

  // Init the database
  ...
  // Load the items from the db
  FMResultSet *s = [database executeSQL:@"SELECT id, termtext, langid, normalized FROM    terms ORDER BY normalized ASC"];

  while ([s next]) 

    Term* term = [Term new];
    term.termId = [s intForColumn:@"id"];

    // Add the loaded item to termResults on the main thread
    // and refresh the table view
    dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

    dispatch_async(mainDispatchQueue, ^() 
      // Add the loaded item to termResults
      [termResults addObject:term];
      // Refresh the table view. This will only reload the visible items.
      [tableView reloadData];
    );

    [term release];
  
);

希望这会有所帮助。

【讨论】:

【参考方案3】:

从 sqlite 读取 10K 记录到 UITableView 中没有问题。首先,我看不出这样做的理由。其次,由于大量内存分配,您无法避免时间间隔。内存分配是一个系统调用。而且系统调用通常很慢。在加载期间,ios 将发送内存警告以运行应用程序,从而导致更长的间隙。如果你需要一百万条记录,你会怎么做? sqlite 用于完全避免这种内存数据结构。如果您发现在 iphone (icu) 上的 sqlite 中使用 unicode 出现问题 - 请阅读 here。我在我的几个应用程序中使用了这种技术,并且所有这些应用程序都毫无问题地被批准到 AppStore。

【讨论】:

目前我不太关心 icu——“标准化”字段来自打包数据——我将它们放在 mysql 上并使用脚本将它们打包到 sqlite 并添加使用 php 的 intl(pecl 扩展)添加规范化(变音符号/不区分大小写)字段。我还可以执行以下操作:将它们加载到 uitableview 类,但加载仍然需要时间。我见过其他应用程序,例如 Blacklaw 的字典应用程序,可以立即加载术语 (16K+),这就是让我首先寻找答案的原因。 @Panagiotis,为什么您真的需要将所有字典条目加载到内存中? UiTableView 的构造使其不需要加载到内存中的条目多于屏幕上显示的条目。您确定您提到的应用程序在启动时会将所有 16K 条目加载到内存中吗?不可能读取 10K 条记录,为每个条目分配几块内存而没有几秒钟的间隙。 我将它们加载到内存中,以便之后使用 UISearchViewController 所需的 NSPredicate 过滤 (icu)。所以无论哪种方式,它都需要先加载更大的数组。起初我想将 uitableviewing 分成 10-20 个词的块,但后来搜索被限制为 10-20 个词。 @Panagiotis,我强烈建议您使 sqlite unicode 兼容,这很容易。我在回答中给了你 URL,解释了如何做到这一点。您需要将两个文件导入到您的项目中。没什么难的。

以上是关于在 iphone SQLite (FMDB) 中加载 10k+ 行的主要内容,如果未能解决你的问题,请参考以下文章

在使用 FMDB 执行 sqlite 查询时受到打击

FMDB 如何让 sqlite 更简单 iOS?

关于 SQLite iphone 的帮助

FMDB 和 UITableView

iPhone 上的 SQLite 数据库

iPhone 上的最佳 SQLite 实践 [关闭]