如何在iPhone上读取大型UTF-8文件?

时间:2012-01-19 15:29:16

标签: iphone objective-c ios

我的应用程序以UTF-8格式下载文件,该文件太大而无法使用NSString initWithContentsOfFile方法阅读。我遇到的问题是NSFileHandle readDataOfLength方法读取指定数量的字节,我最终可能只读取UTF-8字符的一部分。这里最好的解决方案是什么?

后来:

让它在船舶日志中记录下面的代码:

    NSData *buf = [NSData dataWithContentsOfFile:path
                                      options:NSDataReadingMappedIfSafe
                                        error:nil];

NSString *data = [[[NSString alloc] 
                   initWithBytesNoCopy:(void *)buf.bytes 
                   length:buf.length 
                   encoding:NSUTF8StringEncoding 
                   freeWhenDone:NO] autorelease];

我的主要问题实际上是编码,而不是读取文件的任务。

4 个答案:

答案 0 :(得分:13)

您可以使用NSData +dataWithContentsOfFile:options:error:NSDataReadingMappedIfSafe选项将文件映射到内存而不是加载它。因此,我们将使用iOS中的虚拟内存管理器来确保文件的各个部分以与桌面操作系统处理其磁盘上虚拟内存文件相同的方式交换进RAM。因此,您不需要足够的RAM来将整个文件保存在内存中,您只需要将文件足够小以适应处理器的地址空间(因此,千兆字节)。您将获得一个与普通NSData完全相同的对象,它可以为您节省大部分与使用NSFileHandle和手动流式传输相关的麻烦。

您可能需要将部分转换为NSString,因为您可以真实地期望将UTF-8转换为其他格式(尽管它可能不会;但值得使用-initWithData:encoding:并且看看NSString是否足够智能只是为了保持对原始数据的引用并按需扩展UTF-8),我认为这正是你的问题所在。

我建议您使用-initWithBytes:length:encoding:将合理数量的字节转换为字符串。然后,您可以使用-lengthOfBytesUsingEncoding:找出它实际感知的字节数,并适当地推进您的读指针。这是一个安全的假设,NSString将丢弃您提供的字节末尾的任何部分字符。

编辑:所以,像:

// map the file, rather than loading it
NSData *data = [NSData dataWithContentsOfFile:...whatever...
                         options:NSDataReadingMappedIfSafe
                         error:&youdDoSomethingSafeHere];

// we'll maintain a read pointer to our current location in the data
NSUinteger readPointer = 0;

// continue while data remains
while(readPointer < [data length])
{
    // work out how many bytes are remaining
    NSUInteger distanceToEndOfData = [data length] - readPointer;

    // grab at most 16kb of them, being careful not to read too many
    NSString *newPortion = 
         [[NSString alloc] initWithBytes:(uint8_t *)[data bytes] + readPointer
                 length:distanceToEndOfData > 16384 ? 16384 : distanceToEndOfData
                 encoding:NSUTF8StringEncoding];

    // do whatever we want with the string
    [self doSomethingWithFragment:newPortion];

    // advance our read pointer by the number of bytes actually read, and
    // clean up
    readPointer += [newPortion lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
    [newPortion release];
}

当然,一个隐含的假设是所有UTF-8编码都是唯一的,我不得不承认这些编码不足以说明绝对肯定。

答案 1 :(得分:3)

实际上很容易判断你是否在UTF-8中拆分了多字节字符。连续字符都有两个最重要的位设置如下:10xxxxxx。因此,如果缓冲区的最后一个八位字节具有该模式,则向后扫描以查找不具有该格式的八位字节。这是角色的第一个八位字节。八位字节中最重要的0的位置告诉您角色中有多少个八位字节

0xxxxxxx => 1 octet (ASCII)
110xxxxx => 2 octets
1110xxxx => 3 octets

以及最多6个八位字节。

因此,要弄清楚要读取多少个额外的八位字符才能到达字符边界是非常简单的。

答案 2 :(得分:2)

一种方法是

  1. 读到某一点 -
  2. 然后检查最后一个字节以确定它是否正在拆分UTF-8字符
  3. 如果没有 - 阅读下一个块
  4. 如果是,请获取下一个字节并修复 - 然后读取下一个块

答案 3 :(得分:0)

utf8是自同步的 - 只需要根据需要多读或少读,然后读取字节值以确定任何代码点的边界。

另外,您可以使用fopen并在堆栈上使用一个小的,可管理的缓冲区,而内存不会成为问题。