在Swift中如何将[Int8]转换为[UInt8]

6

我有一个只包含字符的缓冲区

let buffer: [Int8] = ....

然后我需要将这个传递给一个接受 [UInt8] 参数的 process 函数。

func process(buffer: [UInt8]) {
    // some code
}

什么是将[Int8]缓冲区转换为[Int8]的最佳方法?我知道以下代码可以工作,但在这种情况下,缓冲区只包含一堆字符,使用map等函数是不必要的。
process(buffer.map{ x in UInt8(x) }) // OK
process([UInt8](buffer)) // error
process(buffer as! [UInt8]) // error

我正在使用Xcode7 b3 Swift2。

1
“不必使用诸如map之类的函数”所以你询问“最好的方式”,但是你又说这种方式是“不必要的”?通过排除答案,你使自己的问题毫无意义。 - matt
1
没错,我只是想找到如何转换缓冲区而不是处理每个单独的字节,消耗额外的 CPU 开销。 - Kaz Yoshikawa
4个回答

5

在我的看法中,最好的方法是在整个应用程序中始终使用相同的基础类型,以避免需要进行强制转换。也就是说,在所有地方都使用 Int8UInt8 中的一个,而不是两者混用。

如果你没有选择,比如使用两个你无法控制的框架,其中一个使用Int8,而另一个使用UInt8,那么如果你真的想使用Swift,可以使用map。你例子中后两行(process([UInt8](buffer))process(buffer as! [UInt8]))更像是C语言解决该问题的方式,即我们不关心这个内存区域是带符号整数数组还是无符号整数数组,现在我们将其视为无符号整数数组。这基本上抛弃了Swift强类型的思想。

我可能会尝试使用lazy序列。例如,检查是否可以使用以下内容提供给process()方法:

let convertedBuffer = lazy(buffer).map() {
    UInt8($0)
}

process(convertedBuffer)

这样做至少可以避免额外的内存开销(否则您将不得不保留2个数组),并且可能会提高性能(由于惰性)。

感谢指向lazy。它在Swift1.2上运行良好,但在beta 3下的Swift 2中编译时出现错误,我想知道为什么... - Kaz Yoshikawa
奇怪。我在XCode 7 beta 3 playground中尝试了这段代码。你的代码是什么,出现了什么错误?也许值得再问一下。 - 0x416e746f6e

5

我基本上同意其他答案,你应该只使用map,但是,如果你的数组真的非常巨大,并且为了将其转换为相同的位模式而创建整个第二个缓冲区真的很痛苦,那么可以像这样做:

// first, change your process logic to be generic on any kind of container
func process<C: CollectionType where C.Generator.Element == UInt8>(chars: C) {
    // just to prove it's working...
    print(String(chars.map { UnicodeScalar($0) }))
}

// sample input
let a: [Int8] = [104, 101, 108, 108, 111]  // ascii "Hello"

// access the underlying raw buffer as a pointer
a.withUnsafeBufferPointer { buf -> Void in
    process(
        UnsafeBufferPointer(
            // cast the underlying pointer to the type you want
            start: UnsafePointer(buf.baseAddress), 
            count: buf.count))
}
// this prints [h, e, l, l, o]

注意,withUnsafeBufferPointer 的含义就是它的字面意思。如果使用不当,它是不安全的,可能会破坏内存(特别要小心使用计数器)。它基于你已知的外部知识来工作,例如,如果任何整数为负数,则您的代码不会在它们变成损坏的无符号整数时感到困扰。您可能知道这一点,但Swift类型系统不知道,因此它不允许在不使用不安全类型的情况下执行此操作。

尽管如此,上述代码是正确的,并且在规则内,这些技术是有正当理由的,前提是您需要性能优势。除非您处理大量数据或编写将被调用无数次的库,否则您几乎肯定不需要它们。

值得注意的是,存在某些情况下数组并不是由一个连续的缓冲区支持的(例如,如果它从 NSArray 转换而来),在这种情况下,调用.withUnsafeBufferPointer 会先将所有元素复制到一个连续的数组中。另外,Swift 数组是可变大小的,所以在数组增长时会经常发生底层元素的拷贝。如果性能绝对关键,您可以考虑使用 UnsafeMutablePointer 来分配自己的内存,并使用 UnsafeBufferPointer 风格来使用它。

对于一个有趣但绝对不符合规则的例子,您不应该使用以下代码:

process(unsafeBitCast(a, [UInt8].self))

值得注意的是,这些解决方案与 a.map { UInt8($0) } 不同,因为后者如果传入负整数会在运行时触发陷阱。如果存在这种可能性,则需要先进行过滤。

感谢您的技巧和想法。一般来说,我同意关注有符号或无符号是很重要的。但是我不确定字符和二进制数据是否有符号或无符号是多么重要 :-)无论如何,我的极端情况 unsafeBitCast 应该足以让我将 utf8 字符串传递给只接受 [UInt8] 的第三方库。 - Kaz Yoshikawa

3
你不能在Swift中强制转换数组。看起来像可以,但实际上是逐个转换每个元素。因此,只有当元素可以转换时,才能使用带有数组的转换符号。
在Swift中,你不能在数字类型之间进行强制转换。你必须进行强制转换,这是完全不同的事情 - 也就是说,你必须基于原始对象创建一个不同数字类型的新对象。想要在期望UInt8的地方使用Int8,唯一的方法是进行强制转换:UInt8(x)
对于一个Int8所说的内容也适用于整个Int8数组。你不能将Int8数组强制转换为UInt8数组,就像你不能将它们中的任何一个强制转换一样。要得到一个UInt8数组,唯一的方法是强制转换所有元素。这正是你的map调用所做的。这是正确的方法;说它是“不必要的”是没有意义的。

你确实具有缓冲区,可以使用“withUnsafeBufferPointer”来访问数组的基础缓冲区。 - Airspeed Velocity
@AirspeedVelocity 我担心你会问到这个问题。 :) - matt
@AirspeedVelocity,你认为我应该删除我的回答吗?我不想误导任何人。 - matt
嗯,你说的“casting”不合理是对的,我会删除“no buffer comment”。至少你没有误导人们进入不安全的领域 :) - Airspeed Velocity
@AirspeedVelocity 好的,我更喜欢让你来做那个!:))))) - matt

-1
一个单独的无符号字节(UInt8)可以通过使用将其视为有符号整数字节(Int8)来进行转换。
Int8(bitPattern: uInt8)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接