我试图从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
}
答案 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"