如何高效地在Core Data中插入和获取UUID

26

我正在寻找一种高效的方法来存储和搜索 Core Data 中的 UUID。这些 UUID 是由许多 iOS 设备在分布式系统中生成的,每个设备可能会存储大约 20-50k 个 UUID。

显然,将 UUID 存储为字符串在索引效率上会有影响。但是经过一系列研究后,我发现将 UUID 存储为二进制数据并在 Core Data 中进行索引可能比将其存储为字符串还要低效

由于 SQLite 不支持类似 BINARY 或 VARBINARY 的数据类型。我猜测任何 Core Data 中的二进制数据类型都会被存储为 SQLite 中的 BLOB。由于 BLOB 可能是最慢的数据类型之一,它会对性能产生不良影响。

那么有没有更有效的方法来存储 Core Data 中的 UUID 呢?请帮忙回答一下。


你知道自iOS 5起,访问UDID已被弃用了吗? - Michael Dautermann
7
OP谈论的是UUID,这与UDID不同。 - Jody Hagins
@JodyHagins,你说得对。我提到的UUID是由我的应用程序创建的ManagedObjects的通用唯一标识符。 - Cable W
2个回答

52
将它们存储为ASCII字符串,并将字段设置为索引。
编辑:
天哪,我碰巧在瞎逛时发现了这个。真是个丢人的回答。那天可能心情有点不好。如果可以的话,我会直接删除并继续前进。但很遗憾,这是不可能的,所以我会提供一个更新的片段。
首先,了解什么是“高效”的唯一方法就是进行测量,考虑程序时间和空间以及源代码复杂性和程序员的努力。
幸运的是,这个问题相当简单。
我编写了一个非常简单的OSX应用程序。模型只包含一个属性:identifier。
如果您不将属性标记为索引,则所有这些都无关紧要。创建存储时需要更多时间,但查询速度会更快。
此外,请注意,为二进制属性创建谓词与为字符串创建谓词完全相同。
fetchRequest.predicate =
    [NSPredicate predicateWithFormat:@"identifier == %@", identifier];

应用程序非常简单。首先,它创建N个对象,并为标识属性分配一个UUID。每500个对象保存一次MOC。然后,我们将所有标识符存储到一个数组中,并对它们进行随机洗牌。整个CD堆栈被完全拆除,以将其从内存中移除。
接下来,我们重新构建堆栈,然后迭代标识符,并进行简单的获取操作。获取对象是通过构造一个简单的谓词来获取一个对象。所有这些都在自动释放池中完成,以尽可能保持每次获取的纯净性(我承认与CD缓存会有一些交互)。这并不是很重要,因为我们只是在比较不同的技术。
二进制标识符是UUID的16字节。
UUID字符串是一个36字节的字符串,调用[uuid UUIDString]的结果,它看起来像这样(B85E91F3-4A0A-4ABB-A049-83B2A8E6085E)。
Base64字符串是一个24字节的字符串,是对16字节UUID二进制数据进行Base64编码的结果,它看起来像这样(uF6R80oKSrugSYOyqOYIXg==),对应相同的UUID。
计数是该运行中对象的数量。
SQLite大小是实际sqlite文件的大小。
WAL大小是WAL(预写式日志)文件的大小 - 只是提供信息...
创建是创建数据库所需的秒数,包括保存。
查询是查询每个对象所需的秒数。
数据类型 数量 (N) SQLite 大小 WAL 大小 创建时间 查询时间
二进制 100,000 5,758,976 5,055,272 2.6013 9.2669
二进制 1,000,000 58,003,456 4,783,352 59.0179 96.1862
UUID 字符串 100,000 10,481,664 4,148,872 3.6233 9.9160
UUID 字符串 1,000,000 104,947,712 5,792,752 68.5746 93.7264
Base64 字符串 100,000 7,741,440 5,603,232 3.0207 9.2446
Base64 字符串 1,000,000 77,848,576 4,931,672 63.4510 94.5147
首先要注意的是,实际数据库的大小要比存储的字节数(1,600,000和16,000,000)大得多 - 这在数据库中是可以预料的。额外存储量将与实际对象的大小有关...这个数据库只存储标识符,因此开销的百分比会更高。
其次,在速度问题上,作为参考,使用对象ID进行查询相同的1,000,000个对象,需要大约82秒(请注意与调用existingObjectWithID:error:相比,后者仅需0.3065秒的巨大差异)。
您应该对自己的数据库进行性能分析,包括对运行代码使用适当的工具。我想如果我进行多次运行,这些数字可能会有所不同,但它们非常接近,对于这个分析来说并不必要。
然而,基于这些数字,让我们来看一下代码执行的效率测量。
  • 正如预期的那样,以原始UUID二进制数据存储在空间上更有效率。
  • 创建时间非常接近(差异似乎基于创建字符串的时间和额外的存储空间所需)。
  • 查询时间几乎相同,二进制字符串稍微慢一点。我想这是最初的担忧——对二进制属性进行查询。

二进制在空间上占优势,并且在创建时间和查询时间上可以被视为接近平局。如果只考虑这些因素,存储二进制数据是明显的赢家。

那么源代码复杂性和程序员时间呢?

嗯,如果你正在使用现代版本的iOS和OSX,几乎没有区别,尤其是在NSUUID的简单类别上。

然而,有一个考虑因素,那就是在数据库中使用数据的便利性。当你存储二进制数据时,很难对数据进行良好的可视化。

因此,如果出于某种原因,你希望数据库中的数据以更高效的方式存储,供人类使用,那么将其存储为字符串是一个更好的选择。因此,你可能需要考虑使用base64编码(或其他编码方式——但请记住它已经是基于256的编码)。

FWIW,这是一个示例类别,以便更轻松地访问UUID,同时提供NSData和base64字符串的形式:
- (NSData*)data
{
    uuid_t rawuuid;
    [self getUUIDBytes:rawuuid];
    return [NSData dataWithBytes:rawuuid length:sizeof(rawuuid)];
}

- (NSString*)base64String
{
    uuid_t rawuuid;
    [self getUUIDBytes:rawuuid];
    NSData *data = [NSData dataWithBytesNoCopy:rawuuid length:sizeof(rawuuid) freeWhenDone:NO];
    return [data base64EncodedStringWithOptions:0];
}

- (instancetype)initWithBase64String:(NSString*)string
{
    NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
    if (data.length == sizeof(uuid_t)) {
        return [self initWithUUIDBytes:data.bytes];
    }
    return self = nil;
}

- (instancetype)initWithString:(NSString *)string
{
    if ((self = [self initWithUUIDString:string]) == nil) {
        self = [self initWithBase64String:string];
    }
    return self;
}

好的建议,谢谢。这可能会为Core Data节省一半的工作量。但我仍然想知道如何将ASCII字符串从Core Data映射到SQLite。我猜只有运行真正的测试才能告诉答案。 - Cable W
通常情况下,您需要确保搜索的任何字符串都已标准化以排除Unicode。此外,不要使用大小写不敏感的搜索,而是将数据标准化以删除Unicode和大小写。使用<和>代替BEGINSWITH等。在2010年、2011年和2012年的WWDC视频中有很好的建议。我强烈推荐您观看这些视频。 - Jody Hagins
嗨@JodyHagins,你能具体说明一下关于这个主题的WWDC视频名称吗?它们太多了。提前感谢。 - Cable W
嗨@JodyHagins,我尝试使用NSString *utf8String = [NSString stringWithFormat:@"%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c", uuidBytes.byte0, uuidBytes.byte1, ...... uuidBytes.byte15]; 将UUID字节保存为字符串,但结果证明这是一种不可靠的方法。值会发生变化。所以你能建议一种好的方法将UUID转换为ASCII字符串吗? - Cable W
电缆 - 要生成UUID并将其转换为字符串,请使用以下代码(ARC):CFUUIDRef uuidRef = CFUUIDCreate(NULL); NSString *uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, uuidRef); CFRelease(uuidRef); - Marcin
花时间审查简短回答是个好决定。感谢解释性补充 - Wolf

3

鉴于这篇文章似乎相当受欢迎,值得注意的是,自2012年以来情况有所改变。

现在你可以使用NSUUIDA/UUID属性类型(UUIDAttributeType),而不必手动将其映射到字符串或二进制数据中(在iOS 11中添加)。 UUID会自动存储为二进制,这是根据其他答案中所说的,存储UUID在CoreData中最快、最优化的方式。

WWDC17:Core Data中的新功能

[20:21] 我们添加了NSUUIDA属性类型和NSURL属性类型,后者由UUID和URL值类支持。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接