数据库中的拟合Swift字符串VARCHAR(255)

时间:2016-07-11 10:19:08

标签: swift string utf-8 swift2

我试图从Swift字符串中获取最多255个UTF8代码单元的有效子字符串(这个想法是能够将它存储为数据库VARCHAR(255)字段)。

获取子字符串的标准方法是:

let string: String = "Hello world!"
let startIndex = string.startIndex
let endIndex = string.startIndex.advancedBy(255, limit: string.endIndex)
let databaseSubstring1 = string[startIndex..<endIndex]

但很明显,这会给我一个255个字符的字符串,在UTF8表示中可能需要超过255个字节。

对于UTF8,我可以这样写:

let utf8StartIndex = string.utf8.startIndex
let utf8EndIndex = utf8StartIndex.advancedBy(255, limit: string.utf8.endIndex)
let databaseSubstringUTF8View = name.utf8[utf8StartIndex..<utf8EndIndex]
let databaseSubstring2 = String(databaseSubstringUTF8View)

但我冒着最后半个字符的风险,这意味着我的UTF8View不是有效的UTF8序列。 正如预期的那样databaseSubstring2是一个可选字符串,因为初始化程序可能会失败(它定义为public init?(_ utf8: String.UTF8View))。

所以我需要一些方法来在最后剥离无效的UTF8代码点,或者 - 如果可能的话 - 一种内置的方式来做我在这里尝试做的事情。

修改

事实证明数据库理解字符,所以我不应该尝试计算UTF8代码单元,而是计算数据库在字符串中计算的字符数(可能取决于数据库)。

根据@OOPer,MySQL将字符计为UTF-16代码单元。我想出了以下实现:

private func databaseStringForString(string: String, maxLength: Int = 255) -> String
{
    // Start by clipping to 255 characters
    let startIndex = string.startIndex
    let endIndex = startIndex.advancedBy(maxLength, limit: string.endIndex)
    var string = string[startIndex..<endIndex]

    // Remove characters from the end one by one until we have less than
    // the maximum number of UTF-16 code units
    while (string.utf16.count > maxLength) {
        let startIndex = string.startIndex
        let endIndex = string.endIndex.advancedBy(-1, limit: startIndex)
        string = string[startIndex..<endIndex]
    }
    return string
}

这个想法是计算UTF-16代码单元,但是从最后删除字符(这就是Swift认为字符是什么)。

编辑2

仍然根据@OOPer,Posgresql将字符计为unicode标量,所以这应该可行:

private func databaseStringForString(string: String, maxLength: Int = 255) -> String
{
    // Start by clipping to 255 characters
    let startIndex = string.startIndex
    let endIndex = startIndex.advancedBy(maxLength, limit: string.endIndex)
    var string = string[startIndex..<endIndex]

    // Remove characters from the end one by one until we have less than
    // the maximum number of Unicode Scalars
    while (string.unicodeScalars.count > maxLength) {
        let startIndex = string.startIndex
        let endIndex = string.endIndex.advancedBy(-1, limit: startIndex)
        string = string[startIndex..<endIndex]
    }
    return string
}

1 个答案:

答案 0 :(得分:1)

在我的评论中写道,您可能需要databaseStringForString(_:maxLength:)截断字符串以匹配DBMS的长度限制。 PostgreSQL使用utf8,MySQL使用utf8mb4。

我会编写与您的编辑2相同的功能:

func databaseStringForString(string: String, maxUnicodeScalarLength: Int = 255) -> String {
    let start = string.startIndex
    for index in start..<string.endIndex {
        if string[start..<index.successor()].unicodeScalars.count > maxUnicodeScalarLength {
            return string[start..<index]
        }
    }
    return string
}

这可能效率较低,但有点短。

let s = "abc\u{1D122}\u{1F1EF}\u{1F1F5}" //->"abc"

let dbus = databaseStringForString(s, maxUnicodeScalarLength: 5) //->"abc"(=="abc\u{1D122}")

所以,使用utf8(= utf8mb3)与MySQL合作的人需要这样的东西:

func databaseStringForString(string: String, maxUTF16Length: Int = 255) -> String {
    let start = string.startIndex
    for index in start..<string.endIndex {
        if string[start..<index.successor()].utf16.count > maxUTF16Length {
            return string[start..<index]
        }
    }
    return string
}

let dbu16 = databaseStringForString(s, maxUTF16Length: 4) //->"abc"