22

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

很明显,在 Core Data 中将 UUID 存储为 String 会损害对其进行索引的效率。但经过一系列研究后,我发现将 UUID 存储为 Core Data 中的二进制数据(并对其进行索引)可能比将其存储为 String 效率低

因为在 SQLit 中不支持 BINARY-like 或 VARBINARY-like 数据类型。我猜 Core Data 中的任何二进制数据类型的数据都在 SQLit 中存储为 BLOB。由于 BLOB 可能是被索引的最慢的数据类型,因此会对性能造成不良影响。

那么任何人都可以帮助回答,有没有更有效的方法将 UUID 存储在 Core Data 中?

4

2 回答 2

45

将它们存储为 ASCII 字符串,并使字段成为索引。

编辑

Egads,我碰巧在做一些事情,并遇到了这个。多么可耻的回答。那天我一定是有点心情。如果可以的话,我会删除它并继续前进。但是,这是不可能的,所以我将提供一个更新的片段。

首先,了解什么是“高效”的唯一方法是衡量,考虑程序时间和空间以及源代码复杂性和程序员工作量。

幸运的是,这很容易。

我写了一个非常简单的 OSX 应用程序。该模型由一个属性组成:identifier.

如果您不将属性标记为索引,这些都不重要。创建商店会花费更多时间,但它会使查询速度更快。

另外,请注意,为二进制属性创建谓词与为字符串创建谓词完全相同:

fetchRequest.predicate =
    [NSPredicate predicateWithFormat:@"identifier == %@", identifier];

该应用程序非常简单。首先,它创建 N 个对象,并为标识符属性分配一个 UUID。它每 500 个对象保存一次 MOC。然后我们将所有标识符存储到一个数组中并随机打乱它们。然后将整个 CD 堆栈完全拆除以将其全部从内存中删除。

接下来,我们再次构建堆栈,然后遍历标识符,并进行简单的获取。构造 fetch 对象,使用一个简单的谓词来获取该对象。所有这些都是在自动释放池中完成的,以尽可能保持每次提取的原始状态(我承认会与 CD 缓存进行一些交互)。这不是那么重要,因为我们只是比较不同的技术。

二进制标识符是 UUID 的 16 字节。

UUID String是一个36字节的字符串,调用[uuid UUIDString]的结果,看起来是这样的(B85E91F3-4A0A-4ABB-A049-83B2A8E6085E)。

Base64 String 是一个 24 字节的字符串,对 16 字节的 UUID 二进制数据进行 base-64 编码的结果,对于相同的 UUID,它看起来像这样 (uF6R80oKSrugSYOyqOYIXg==)。

Count 是该运行的对象数。

SQLite 大小是实际 sqlite 文件的大小。

WAL 大小是 WAL(预写日志)文件的大小 - 仅供参考......

create 是创建数据库的秒数,包括保存。

Query 是查询每个对象的秒数。

Data Type     | Count (N) | SQLite Size | WAL Size  | Create  | Query
--------------+-----------+-------------+-----------+---------+---------
Binary        |   100,000 |   5,758,976 | 5,055,272 |  2.6013 |  9.2669
Binary        | 1,000,000 |  58,003,456 | 4,783,352 | 59.0179 | 96.1862
UUID String   |   100,000 |  10,481,664 | 4,148,872 |  3.6233 |  9.9160
UUID String   | 1,000,000 | 104,947,712 | 5,792,752 | 68.5746 | 93.7264
Base64 String |   100,000 |   7,741,440 | 5,603,232 |  3.0207 |  9.2446
Base64 String | 1,000,000 |  77,848,576 | 4,931,672 | 63.4510 | 94.5147

首先要注意的是,实际的数据库大小比存储的字节数(1,600,000 和 16,000,000)大得多——这对于数据库来说是可以预料的。额外存储的数量将在某种程度上与您的实际对象的大小有关……这个仅存储标识符,因此开销的百分比会更高)。

其次,在速度问题上,作为参考,执行相同的 1,000,000 个对象查询,但在 fetch 中使用 object-id 大约需要 82 秒(注意这与调用之间的明显差异,existingObjectWithID:error:需要 0.3065 秒)。

您应该分析您自己的数据库,包括在运行代码上明智地使用工具。我想如果我进行多次运行,数字会有所不同,但它们非常接近,因此没有必要进行此分析。

但是,基于这些数字,让我们看看代码执行的效率测量。

  • 正如预期的那样,存储原始 UUID 二进制数据在空间方面更有效。
  • 创建时间非常接近(差异似乎基于创建字符串的时间和所需的额外存储空间)。
  • 查询时间似乎几乎相同,二进制字符串似乎有点慢。我认为这是最初的关注点——对二进制属性进行查询。

Binary 大大赢得了空间,它可以被认为在创建时间和查询时间上都非常接近。如果我们只考虑这些,存储二进制数据显然是赢家。

源代码复杂性和程序员时间如何?

好吧,如果您使用的是现代版本的 iOS 和 OSX,则几乎没有区别,尤其是 NSUUID 上的简单类别。

但是,您需要考虑一个问题,那就是使用数据库中的数据是否容易。当您存储二进制数据时,很难对数据进行良好的可视化。

因此,如果出于某种原因,您希望数据库中的数据以对人类更有效的方式存储,那么将其存储为字符串是更好的选择。因此,您可能需要考虑 base64 编码(或其他一些编码——尽管记住它已经在 base-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;
}
于 2012-07-05T03:28:32.423 回答
1

由于这篇文章似乎相当受欢迎,值得注意的是,自 2012 年以来情况发生了一些变化。

您现在可以使用NSUUIDA/UUID属性类型 ( UUIDAttributeType ) 而不是手动将其映射到字符串或二进制数据(在 iOS 11 中添加)。UUID 将自动存储为二进制文件,根据另一个答案,这是在 CoreData 中存储 UUID 的最快、最佳方式。

WWDC17:核心数据的新功能

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

于 2020-12-15T11:43:43.733 回答