首页 > 解决方案 > Swift:高性能字符串转换?

问题描述

我有大约 36000 个文件,每个文件包含大约 250 个单词,我需要通过删除每个小数字符并将所有大写字符小写来转换每个单词。

我在一个函数中传递我读到的每个单词,该函数返回修改后的单词,以便我稍后对其进行索引。

问题是,有这么大量的字符,它超级慢。

我一直在尝试迭代我的字符串,Characters像在 C 中一样操作,使用 ascii 值,但是 Swift 对此有点糟糕,需要一直从 Characters 重建字符串。

我带来了这段小代码,但我在 Swift 中对这个精确主题的了解是广泛的,可以找到更好的。另外,我使用的是 Swift 4.2。

func processToken(token: String) -> String {
    var result = String()

    for ascii in token.utf8 {
        if ascii > 64 && ascii < 91 {
            result.append(String(UnicodeScalar(UInt8(ascii + 32))))
        }
        else if ascii > 96 && ascii < 123 {
            result.append(String(UnicodeScalar(UInt8(ascii))))
        }
    }
    return result
}

标签: swiftstringnsstring

解决方案


我写了一个应该很快的解决方案。这是一个 Objective-C 函数,主要使用普通的旧 C。为什么是 C?因为我们确定 C-runtime 的作用是提供一个超薄的内存包装器。另一方面,Swift 运行时的许多低级细节是未知的,并且有充分的理由。但是,可以安全地假设 Swift 具有比普通 C 更高的性能开销。在这方面击败 C 的唯一方法是一直到汇编程序或打开您选择的十六进制编辑器并输入操作码和操作数自己。不要误解我的意思,出于很多原因,应该始终首选 Swift,但如果性能至关重要,那么 Objective-C / C 是首选工具。

所以这里是代码片段。它需要一个 NSString 并删除所有数字并将所有大写字符转换为对应的小写字符。

-  ( NSString* ) convert:( NSString* ) source
{
    const char bitmaskNonAsciiChar =  ( char ) ( 1 << 7 );
    const char* cSource = [ source UTF8String ];
    size_t strLength = strlen( cSource );
    size_t bufferSize = sizeof( char ) * strLength + 1;

    char *result = ( char* ) malloc( bufferSize );
    memset( result, '\0', bufferSize );

    int currentIndex = 0;
    for( int i = 0; i < strLength; ++i )
    {
        // Check if this is an UTF-8 character that's longer than one byte. If so, it can't be an ASCII character and we can just write this character to the result buffer.
        if( ( cSource[ i ] & bitmaskNonAsciiChar ) == bitmaskNonAsciiChar )
        {
            result[ currentIndex++ ] = cSource[ i ];
            continue;
        }

        // Now we know it is an ASCII character, so we need to check for digits and upper-case characters.
        if( cSource[ i ] >= ( char ) 46 && cSource[ i ] <= ( char ) 57 )
        {
            continue;
        }

        if( cSource[ i ] >= ( char ) 65 && cSource[ i ] <= ( char ) 90 )
        {
            result[ currentIndex++ ] = cSource[ i ] + ( char ) 32;
            continue;
        }

        // It's an ASCII character that is neither a digit nor upper-cased, so we just write it to the result buffer.
        result[ currentIndex++ ] = cSource[ i ];
    }

    NSString *resultString = [ NSString stringWithUTF8String: result ];
    free( result );

    return resultString;
}

这是一个稍微修改过的版本,如果您确定输入大小相当小,可以使用它。这个版本不使用 malloc 在堆上创建结果缓冲区,而是简单地使用堆栈内存。如果您想逐字运行,那么为每次调用分配所有这些内存是浪费大量时间。但是,如果您打算逐个文件地运行它并且您不知道文件可能有多大,那么第一个版本可能会更好。

-  ( NSString* ) convert:( NSString* ) source
{
    const char bitmaskNonAsciiChar =  ( char ) ( 1 << 7 );
    const char* cSource = [ source UTF8String ];
    size_t strLength = strlen( cSource );
    size_t bufferSize = sizeof( char ) * strLength + 1;

    char result[ bufferSize ];
    memset( result, '\0', bufferSize );

    int currentIndex = 0;
    for( int i = 0; i < strLength; ++i )
    {
        // Check if this is an UTF-8 character that's longer than one byte. If so, it can't be an ASCII character and we can just write this character to the result buffer.
        if( ( cSource[ i ] & bitmaskNonAsciiChar ) == bitmaskNonAsciiChar )
        {
            result[ currentIndex++ ] = cSource[ i ];
            continue;
        }

        // Now we know it is an ASCII character, so we need to check for digits and upper-case characters.
        if( cSource[ i ] >= ( char ) 46 && cSource[ i ] <= ( char ) 57 )
        {
            continue;
        }

        if( cSource[ i ] >= ( char ) 65 && cSource[ i ] <= ( char ) 90 )
        {
            result[ currentIndex++ ] = cSource[ i ] + ( char ) 32;
            continue;
        }

        // It's an ASCII character that is neither a digit nor upper-cased, so we just write it to the result buffer.
        result[ currentIndex++ ] = cSource[ i ];
    }

    NSString *resultString = [ NSString stringWithUTF8String: result ];

    return resultString;
}

推荐阅读