繁体   English   中英

将大型CSV文件加载到核心数据的最快方法是什么

[英]What is the fastest way to load a large CSV file into core data

结论
问题关闭,我想。
看起来问题与方法无关,但是XCode没有在构建之间正确地清理项目。
看起来在所有这些测试之后,正在使用的sqlite文件仍然是第一个没有编入索引的文件......
谨防XCode 4.3.2,我没有任何问题,干净不清理,或添加文件到项目不会自动添加到捆绑资源...
谢谢你的答案......

更新3
由于我邀请任何人尝试相同的步骤来查看他们是否得到相同的结果,让我详细说明我做了什么:
我从空白项目开始
我定义了一个带有一个实体的数据模型,3个属性(2个字符串,1个浮点数)
第一个字符串已编入索引
在此输入图像描述

在doneLaunchingWithOptions中,我打电话给:

[self performSelectorInBackground:@selector(populateDB) withObject:nil];

populateDb的代码如下:

-(void)populateDB{
NSLog(@"start");
NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
NSManagedObjectContext *context;
if (coordinator != nil) {
    context = [[NSManagedObjectContext alloc] init];
    [context setPersistentStoreCoordinator:coordinator];
}

NSString *filePath = [[NSBundle mainBundle] pathForResource:@"input" ofType:@"txt"];  
if (filePath) {  
    NSString * myText = [[NSString alloc]
                               initWithContentsOfFile:filePath
                               encoding:NSUTF8StringEncoding
                               error:nil];
    if (myText) {
        __block int count = 0;


        [myText enumerateLinesUsingBlock:^(NSString * line, BOOL * stop) {
            line=[line stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
            NSArray *lineComponents=[line componentsSeparatedByString:@" "];
            if(lineComponents){
                if([lineComponents count]==3){
                    float f=[[lineComponents objectAtIndex:0] floatValue];
                    NSNumber *number=[NSNumber numberWithFloat:f];
                    NSString *string1=[lineComponents objectAtIndex:1];
                    NSString *string2=[lineComponents objectAtIndex:2];
                    NSManagedObject *object=[NSEntityDescription insertNewObjectForEntityForName:@"Bigram" inManagedObjectContext:context];
                    [object setValue:number forKey:@"number"];
                    [object setValue:string1 forKey:@"string1"];
                    [object setValue:string2 forKey:@"string2"];
                    NSError *error;
                    count++;
                    if(count>=1000){
                        if (![context save:&error]) {
                            NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
                        }
                        count=0;

                    }
                }
            }



        }];
        NSLog(@"done importing");
        NSError *error;
        if (![context save:&error]) {
            NSLog(@"Whoops, couldn't save: %@", [error localizedDescription]);
        }

    }  
}
NSLog(@"end");
}

其他一切都是默认的核心数据代码,没有添加。
我在模拟器中运行它。
我去〜/ Library / Application Support / iPhone Simulator / 5.1 / Applications // Documents
有生成的sqlite文件

我接受了,并将其复制到我的包中

我注释掉了populateDb的调用

我编辑persistentStoreCoordinator在第一次运行时将sqlite文件从bundle复制到文档

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator 
{
@synchronized (self)
{
    if (__persistentStoreCoordinator != nil)
        return __persistentStoreCoordinator;

    NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:@"myProject" ofType:@"sqlite"];
    NSString *storePath = [[[self applicationDocumentsDirectory] path] stringByAppendingPathComponent: @"myProject.sqlite"];

    NSError *error;
    if (![[NSFileManager defaultManager] fileExistsAtPath:storePath]) 
    {
        if ([[NSFileManager defaultManager] copyItemAtPath:defaultStorePath toPath:storePath error:&error])
            NSLog(@"Copied starting data to %@", storePath);
        else 
            NSLog(@"Error copying default DB to %@ (%@)", storePath, error);
    }

    NSURL *storeURL = [NSURL fileURLWithPath:storePath];

    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];

    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                             [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                             [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];

    if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) 
    {

        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }    

    return __persistentStoreCoordinator;
}    
}


我从模拟器中删除了应用程序,我检查〜/ Library / Application Support / iPhone Simulator / 5.1 / Applications /现已删除
我重建并重新启动
正如所料,sqlite文件被复制到〜/ Library / Application Support / iPhone Simulator / 5.1 / Applications // Documents

但是,文件的大小明显小于捆绑包中的大小! 另外,使用类似谓词的谓词进行简单查询= [NSPredicate predicateWithFormat:@“string1 ==%@”,string1]; 清楚地表明string1不再被索引

接下来,我创建了一个新版本的datamodel,带有无意义的更新,只是为了强制轻量级迁移
如果在模拟器上运行,迁移需要几秒钟,数据库的大小增加一倍,同样的查询现在需要不到一秒的时间而不是几分钟。
这将解决我的问题,强制迁移,但同样的迁移需要3分钟在iPad上并发生在前台。
所以我现在所处的位置,对我来说最好的解决方案仍然是防止索引被删除,在启动时任何其他导入解决方案只需要花费太多时间。
如果您需要更多说明,请告诉我......

更新2
因此,到目前为止,我所获得的最佳结果是使用具有类似数据模型的快速工具生成的sqlite文件来构建核心数据数据库,但在生成sqlite文件时没有设置索引。 然后,我在核心数据应用程序中导入此sqlite文件并设置索引,并允许轻量级迁移。 在新iPad上有2百万的记录,这次迁移还需要3分钟。 最终的应用程序应该有这个记录数的5倍,所以我们仍然需要很长的处理时间。 如果我走这条路,那么新问题就是:可以在后台执行轻量级迁移吗?

更新
我的问题不是如何创建一个工具来填充Core Data数据库,然后将sqlite文件导入我的应用程序。
我知道怎么做,我做了无数次。
但到目前为止,我还没有意识到这种方法会产生一些副作用:在我的情况下,结果数据库中的索引属性在导入sqlite文件时显然得到了“无索引”。
如果您能够验证任何索引数据在转移后仍然被编入索引,我很想知道您是如何继续进行的,或者以其他方式有效地为这样的数据库提供种子的最佳策略。

原版的

我有一个大的CSV文件(数百万行),包含4列,字符串和浮点数。 这适用于iOS应用。

我需要在第一次加载应用程序时将其加载到核心数据中。

在数据可用之前,应用程序几乎不起作用,因此加载时间很重要,因为第一次用户显然不希望应用程序在运行之前需要20分钟才能加载。

现在,我目前的代码在新iPad上花了20分钟来处理一个200万行的csv文件。

我使用后台上下文来不锁定UI,并且每1,000条记录保存上下文

我的第一个想法是在模拟器上生成数据库,然后在首次启动时将其复制/粘贴到文档文件夹中,因为这是播种大型数据库的常见非官方方式。 不幸的是,索引似乎没有在这样的传输中存活,虽然数据库在几秒钟之后就可用,但性能很糟糕,因为我的索引丢失了。 我已经发布了一个关于索引的问题,但似乎没有一个好的答案。

所以我要找的是:

  • 一种提高核心数据中数百万条记录加载性能的方法
  • 如果数据库是在第一次启动时预先加载并移动的,那么这是一种保留索引的方法
  • 处理这种情况的最佳实践。 我不记得使用任何要求我在第一次使用前等待x分钟的应用程序(但也许是The Daily,这是一次糟糕的体验)。
  • 任何创造性的方式让用户在没有意识到的情况下等待:通过教程等进行背景导入...
  • 不使用核心数据?
  • ...

使用在Cocoa上编写的脱机应用程序(比如命令行实用程序)预生成数据库,该应用程序在OS X上运行,并使用与iOS相同的Core Data框架。 您无需担心“索引幸存”或其他任何内容 - 输出是Core Data生成的.sqlite数据库文件,可直接立即供iOS应用程序使用。

只要您可以脱机执行数据库生成,它就是迄今为止最好的解决方案。 我已成功使用此技术自行预生成数据库以进行iOS部署。 查看我之前的问题/答案以获取更多细节。

我刚刚开始使用SQLite,我需要将数据库集成到我的一个应用程序中,这些应用程序将在SQLite数据库中包含大量索引数据。 我希望我可以做一些方法,我可以将我的信息批量插入SQLite文件并将该文件添加到我的项目中。 在发现并阅读了您的问题,提供的答案和众多评论之后,我决定查看SQLite源代码,看看我是否可以解决这个问题。

我最初的想法是,SQLite的iOS实现实际上是抛弃你的索引。 原因是您最初在x86 / x64系统上创建数据库索引。 iOS是ARM处理器,数字处理方式不同。 如果您希望索引速度很快,则应该以这样的方式生成它们,使它们针对将在其中搜索它们的处理器进行优化。

由于SQLite适用于多个平台,因此可以删除在另一个体系结构中创建的任何索引并重建它们。 但是,由于没有人想在第一次访问时等待索引重建,因此SQLite开发人员很可能决定放弃索引。

在深入研究SQLite代码之后,我得出的结论是,这很有可能发生。 如果不是因为处理器架构的原因,我没有找到代码(见analyze.c在和其他元信息sqliteint.h ),其中指数,如果他们意想不到的情境下产生了被删除。 我的预感是驱动这个过程的上下文是如何为现有密钥构建底层b树数据结构。 如果SQLite的当前实例无法使用该键,则会将其删除。

值得一提的是,iOS模拟器只是一个模拟器。 它不是硬件的模拟器。 因此,您的应用程序在运行于x86 / x64处理器的伪iOS设备上运行。

当您的app和SQLite DB加载到iOS设备时,会加载ARM编译的变体,该变体也链接到iOS中的ARM编译库。 我找不到与SQLite相关的ARM特定代码,所以我想Apple必须将它修改为它们的诉讼。 这也可能是问题的一部分。 这可能不是root-SQLite代码的问题,它可能是Apple / ARM编译变体的问题。

我能想到的唯一合理的解决方案是您可以创建一个在iOS机器上运行的生成器应用程序。 运行应用程序,构建密钥,然后从设备中删除SQLite文件。 我想这样的文件可以在所有设备上运行,因为iOS使用的所有ARM处理器都是32位的。

同样,这个答案是一个有根据的猜测。 我打算将你的问题重新标记为SQLite。 希望大师可以找到这个,并能够在这个问题上权衡。 为了自己的利益,我真的很想知道真相。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM