核心数据——高效地查找或创建
Posted
技术标签:
【中文标题】核心数据——高效地查找或创建【英文标题】:Core Data — Find-or-Create Efficiently 【发布时间】:2013-10-15 23:05:10 【问题描述】:根据 Apple 的文档 (link)——
在很多情况下,您可能需要查找现有对象 (已保存在商店中的对象)用于一组离散输入值。 一个简单的解决方案是创建一个循环,然后依次为每个值 执行一次 fetch 判断是否有匹配的持久化 对象等等。这种模式不能很好地扩展。如果你配置文件 您的应用程序使用这种模式,您通常会发现 fetch 是 循环中更昂贵的操作之一(与刚刚相比 迭代项目集合)。更糟糕的是,这种模式转向 将
O(n)
问题转化为O(n^2)
问题。尽可能高效地创建所有托管 单遍中的对象,然后修复任何关系 第二关。例如,如果您导入的数据您知道不会 包含任何重复项(比如因为您的初始数据集为空), 您可以只创建托管对象来表示您的数据,而不是这样做 任何搜索。或者,如果您导入“平面”数据而没有 关系,您可以为整个集合创建托管对象,并 在使用单个大
IN
保存之前清除(删除)任何重复项 谓词。
问题一:考虑到我导入的数据没有任何关系,我该如何实现最后一行的描述。
如果您确实需要遵循“查找或创建”模式 - 比如说因为您是 导入关系信息混杂的异构数据 包含属性信息——您可以优化如何找到现有的 通过将您执行的提取次数减少到最低限度来创建对象。 如何做到这一点取决于您的参考数据量 必须合作。如果您要导入 100 个潜在的新对象,并且 您的数据库中只有 2000 个,获取所有现有的和 缓存它们可能不会造成重大损失(特别是如果 您必须多次执行该操作)。但是,如果你 在你的数据库中有 100,000 个项目,保持的内存压力 那些缓存的可能会让人望而却步。
您可以结合使用 IN 谓词和排序来减少 你对 Core Data 的使用到一个单独的 fetch 请求。
示例代码:
// Get the names to parse in sorted order.
NSArray *employeeIDs = [[listOfIDsAsString componentsSeparatedByString:@"\n"]
sortedArrayUsingSelector: @selector(compare:)];
// create the fetch request to get all Employees matching the IDs
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:
[NSEntityDescription entityForName:@"Employee" inManagedObjectContext:aMOC]];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat: @"(employeeID IN %@)", employeeIDs]];
// Make sure the results are sorted as well.
[fetchRequest setSortDescriptors:
@[ [[NSSortDescriptor alloc] initWithKey: @"employeeID" ascending:YES] ]];
// Execute the fetch.
NSError *error;
NSArray *employeesMatchingNames = [aMOC executeFetchRequest:fetchRequest error:&error];
您最终得到两个排序的数组 - 一个传递了员工 ID 进入 fetch 请求,一个与匹配的托管对象 他们。要处理它们,请按照这些排序列表 步骤:
获取下一个 ID 和 Employee。如果 ID 与员工 ID 不匹配, 为该 ID 创建一个新员工。获取下一个员工:如果 ID 匹配,移动到下一个 ID 和 Employee。
问题 2: 在上面的示例中,我得到了两个如上所述的排序数组。考虑到所有要插入的对象都存在于商店中的最坏情况,我无论如何都看不到我可以在O(n)
时间内解决问题。 Apple 描述了上述两个步骤,但这是一个O(n^2)
工作。对于输入数组中的任何kth
元素,在输出数组的第一个k
元素中可能存在也可能不存在与其匹配的元素。所以在最坏的情况下,复杂度将是O(nC2) = O(n^2)
。
所以,我相信 Apple 正在做的是确保 fetch 只处理一次,即使需要进行 O(n^2)
检查。如果是这样,那么我会这样做;但是有没有其他方法可以有效地做到这一点。
请理解,我不想一次又一次地获取 - 为大小为 100 个标识符的输入数组获取一次。
【问题讨论】:
【参考方案1】:广告。 1 建立关系的事实在这里并不重要。这个解释只是说如果你从例如下载你的数据。远程服务器和您的项目有一些 ID,然后您可以在一个请求中从持久存储中获取它们,而不是在单独的请求中获取每个对象。
广告。 2
Apple 描述了上述两个步骤,但这是一个 O(n^2) 的工作。
不是。请仔细阅读以下几行:
要处理它们,请按照以下步骤遍历排序列表:
获取下一个 ID 和 Employee。如果 ID 与员工 ID 不匹配, 为该 ID 创建一个新员工。获取下一个员工:如果 ID 匹配,移动到下一个 ID 和 Employee。
您同时遍历数组/列表,因此您永远不必进行以下检查:“在输出数组中的前 k 个元素中可能存在也可能不存在与其匹配的元素。”您无需检查之前的元素,因为它们已排序,它们肯定不会包含您感兴趣的对象。
【讨论】:
好的,我明白了,我错了。它基本上是一个 O(n) 过程。但这仍然是第 1 部分。如何实现这一点 - > 或者如果您导入没有关系的“平面”数据,您可以为整个集合创建托管对象并在使用单个大 IN 保存之前清除(删除)任何重复项谓词。 @flippex17:我认为他们的意思是这样。您将托管对象插入到上下文中。然后您获取这些对象的 ID(或其他标识符),将它们放入IN
谓词并从持久存储中获取对象数组(您刚刚插入的对象不会被获取,因为它们还没有保存)。然后您遍历NSManagedObjectContext
的insertedObjects
并删除在持久存储中找到的对象。这基本上是我在回答中写的,只是以不同的顺序实现。
@flippex17:它不适合我之前的评论:您还必须将数组 (fetch array of the objects
) 转换为由您的 IDs
索引的 NSDictionary
以获得摊销 O(1 ) 查找。
当你说 fetch array 时,你的意思是这样的: NSArray *matches = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];我不是专业人士,但我只是想知道如何将其转换为由给定testID
的 ID 索引的 NSDictionary 是ATest
类型的托管对象的字段之一。抱歉,这是一个简单的问题,但我无法解决。
@flippex17:是的,这就是我所说的fetching an array
的意思。你可以像这样得到NSDictionary
:NSMutableDictionary *mutableDict = [[NSMutableDictionary alloc] init]; for (ATest *test in matches) mutableDict[test.testID] = test;
【参考方案2】:
如果有人正在寻找原始 Apple 文档,这里有快照:
http://web.archive.org/web/20150908024050/https://developer.apple.com/library/mac/documentation/cocoa/conceptual/coredata/articles/cdimporting.html
【讨论】:
以上是关于核心数据——高效地查找或创建的主要内容,如果未能解决你的问题,请参考以下文章