在 Swift 中将两个字节的 UInt8 数组转换为 UInt16

Posted

技术标签:

【中文标题】在 Swift 中将两个字节的 UInt8 数组转换为 UInt16【英文标题】:Convert a two byte UInt8 array to a UInt16 in Swift 【发布时间】:2014-08-12 14:35:48 【问题描述】:

使用 Swift 我想将字节从 uint8_t 数组转换为整数。

“C”示例:

char bytes[2] = 0x01, 0x02;
NSData *data = [NSData dataWithBytes:bytes length:2];
NSLog(@"data: %@", data); // data: <0102>

uint16_t value2 = *(uint16_t *)data.bytes;
NSLog(@"value2: %i", value2); // value2: 513

快速尝试:

let bytes:[UInt8] = [0x01, 0x02]
println("bytes: \(bytes)") // bytes: [1, 2]
let data = NSData(bytes: bytes, length: 2)
println("data: \(data)") // data: <0102>

let integer1 = *data.bytes // This fails
let integer2 = *data.bytes as UInt16 // This fails

let dataBytePointer = UnsafePointer<UInt16>(data.bytes)
let integer3 = dataBytePointer as UInt16 // This fails
let integer4 = *dataBytePointer as UInt16 // This fails
let integer5 = *dataBytePointer // This fails

在 Swift 中从 UInt8 数组创建 UInt16 值的正确语法或代码是什么?

我对 NSData 版本感兴趣,正在寻找不使用临时数组的解决方案。

【问题讨论】:

当然,像 Swift 这样的新语言有一种优雅的方式可以将NSData 中接收的数据转换为整数变量。这就是我要找的。​​span> [0x01, 0x02]的uint16是0x0102还是0x0201?这取决于处理器的字节顺序。在java中它是定义的。在 C 中打开。在斯威夫特我不知道。技巧*(uint16)(uint8[2]) 也存在地址对齐问题。 @JoopEggen Endian-ness 不是问题。 更好的是索引数据:let u16 = UnsafePointer&lt;UInt16&gt;(data.bytes)[index] 【参考方案1】:

如果你想通过NSData ,那么它会像这样工作:

let bytes:[UInt8] = [0x01, 0x02]
println("bytes: \(bytes)") // bytes: [1, 2]
let data = NSData(bytes: bytes, length: 2)
print("data: \(data)") // data: <0102>

var u16 : UInt16 = 0 ; data.getBytes(&u16)
// Or:
let u16 = UnsafePointer<UInt16>(data.bytes).memory

println("u16: \(u16)") // u16: 513

或者:

let bytes:[UInt8] = [0x01, 0x02]
let u16 = UnsafePointer<UInt16>(bytes).memory
print("u16: \(u16)") // u16: 513

两种变体都假定字节按主机字节顺序排列。

Swift 3 (Xcode 8) 更新:

let bytes: [UInt8] = [0x01, 0x02]
let u16 = UnsafePointer(bytes).withMemoryRebound(to: UInt16.self, capacity: 1) 
    $0.pointee

print("u16: \(u16)") // u16: 513

【讨论】:

我对@9​​87654325@ 版本感兴趣,但正在寻找不使用临时数组的解决方案。 @Zaph:哪个临时数组?也许let u16 = UnsafePointer&lt;UInt16&gt;(data.bytes).memory 是您要找的? 这正是我要找的!谢谢! let u16 = UnsafePointer&lt;UInt16&gt;(data.bytes).memory @Zaph 确保您确实有更好的性能,并且您确实需要它。使用不安全的代码最终会使你的性能变差,因为编译器可能会忘记你的代码做了什么,它不能用更高效的东西替换你的代码。 @pqnet 我不关心这个级别的性能,当测量显示需要并查明问题所在时,性能是最好的解决方案。至于安全,当然我想要安全的东西,请提供这样的解决方案。请记住,在这种情况下,以及在同一类中的许多其他原始数据字节是在没有 JSON、XML 或其他更高级别格式的情况下接收的。【参考方案2】:

Swift 5 或更高版本中,您可以使用 withUnsafeBytes $0.load(as: UInt16.self) 将字节 [UInt8] 转换为 UInt16

let bytes: [UInt8] = [1, 2]

加载为UInt16

let uint16 = bytes.withUnsafeBytes  $0.load(as: UInt16.self)     // 513 

为了摆脱冗长,我们可以创建一个扩展 ContiguousBytes 的泛型方法:

extension ContiguousBytes 
    func object<T>() -> T 
        withUnsafeBytes  $0.load(as: T.self) 
    


用法:

let bytes: [UInt8] = [1, 2]
let uint16: UInt16 = bytes.object()    // 513

并访问集合中任何位置的字节:

extension Data 
    func subdata<R: RangeExpression>(in range: R) -> Self where R.Bound == Index 
        subdata(in: range.relative(to: self) )
    
    func object<T>(at offset: Int) -> T 
        subdata(in: offset...).object()
    


extension Sequence where Element == UInt8  
    var data: Data  .init(self) 


extension Collection where Element == UInt8, Index == Int 
    func object<T>(at offset: Int = 0) -> T 
        data.object(at: offset)
    


用法:

let bytes: [UInt8] = [255, 255, 1, 2]
let uintMax: UInt16 = bytes.object()      // 65535 at offset zero
let uint16: UInt16 = bytes.object(at: 2)  // 513   at offset two

【讨论】:

【参考方案3】:

怎么样

let bytes:[UInt8] = [0x01, 0x02]
let result = (UInt16(bytes[1]) << 8) + UInt16(bytes[0])

使用循环,这很容易推广到更大的字节数组,并且可以包装在函数中以提高可读性:

let bytes:[UInt8] = [0x01, 0x02, 0x03, 0x04]

func bytesToUInt(byteArray: [UInt8]) -> UInt 
  assert(byteArray.count <= 4)
  var result: UInt = 0
  for idx in 0..<(byteArray.count) 
    let shiftAmount = UInt((byteArray.count) - idx - 1) * 8
    result += UInt(byteArray[idx]) << shiftAmount
  
  return result


println(bytesToUInt(bytes))    // result is 16909060

【讨论】:

是的,let b:UInt16 = UInt16(dataBytePointer[0]) * 256 + UInt16(dataBytePointer[1]) 也可以。 UInt32 非常不雅,并且开始看起来更糟。 为什么 UInt32 更糟?您可以通过循环遍历数组并按当前索引缩放移位量来组装多个字节。 更糟糕的是因为语句变得更长更复杂。 只上4就不用一概而论了。把公式写在一行,手动写班次。如果你坚持一个通用的形式使用递归而不是循环,会更好看。 @pqnet:我喜欢它很容易推广到 2、4 或 8 以分别产生 UInt16、UInt32 或 UInt64。我还打算将循环/函数实现作为对 Zaph 声明的回应,即当模式如此清晰以至于几乎可以轻松实现自动化时,情况会更糟。我个人更喜欢它是一个全 Swift 的解决方案,尽管有人声明了基于 NSData 的解决方案的偏好。我喜欢将其保留在基础语言中并避免不安全的指针构造。【参考方案4】:

我不知道 swift 的语法,但是像这样的东西呢:

let a:UInt16 = UInt16(bytes[0]) * 256 + UInt16(bytes[1])

【讨论】:

关闭,这确实有效:let a:UInt16 = UInt16(bytes[0]) * 256 + UInt16(bytes[1]). @Zaph 我会复制粘贴你的代码,它肯定比我的更正确【参考方案5】:

如果字节在NSData 对象中,您可以这样做(假设data:NSData):

var number: UInt16 = 0
data.getBytes(&number, length: sizeof(UInt16))

getBytes 方法在number 的内存位置最多写入两个字节(类似于C 的memcpy。 如果data 没有足够的字节,这不会使您的应用程序崩溃。

编辑:如果从缓冲区的开头开始,则无需使用范围)

【讨论】:

我希望 Swift 允许安全直接的操作,你能说可选吗?这仍然让人怀疑那里是否有字节为 0,或者是否只有一个或没有。所以仍然需要进行长度检查。只需先进行长度检查,然后让u16 = UnsafePointer&lt;UInt16&gt;(data.bytes)[index] 也是安全的。一旦完成了长度检查,就可以简单地通过索引进一步索引数据。 @Zaph 不管什么人,这是你的代码。我不是负责的人 @Zaph 误会是两个人的错。对不起,我真的以为你在骗我【参考方案6】:

Martin R 的回答非常棒,并且针对 beta 6 进行了很好的更新。 但是,如果您需要获取不在缓冲区开头的字节,则建议的 withMemoryRebound 方法不提供重新绑定的范围。 我对此的解决方案,例如。从数组中挑选出第二个 UInt16 是:

var val: UInt16 = 0
let buf = UnsafeMutableBufferPointer(start: &val, count: 1)
_ = dat.copyBytes(to: buf, from: Range(2...3))

【讨论】:

【参考方案7】:

假设小端编码。

要从 [UInt8] 转换为 UInt16,您可以执行类似的操作

var x: [UInt8] = [0x01, 0x02]
var y: UInt16 = 0
y += UInt16(x[1]) << 0o10
y += UInt16(x[0]) << 0o00

为了转换为 UInt32,此模式扩展到

var x: [UInt8] = [0x01, 0x02, 0x03, 0x04]
var y: UInt32 = 0
y += UInt32(x[3]) << 0o30
y += UInt32(x[2]) << 0o20
y += UInt32(x[1]) << 0o10
y += UInt32(x[0]) << 0o00

移位量的八进制表示可以很好地指示移位了多少完整字节(8 变为 0o10,16 变为 0o20 等)。

对于 UInt16,这可以简化为以下内容:

var x: [UInt8] = [0x01, 0x02]
let y: UInt16 = reverse(x).reduce(UInt16(0)) 
    $0 << 0o10 + UInt16($1)

以及 UInt32 的以下内容:

var x: [UInt8] = [0x01, 0x02, 0x03, 0x04]
let y: UInt32 = reverse(x).reduce(UInt32(0)) 
    $0 << 0o10 + UInt32($1)

精简版也适用于 UInt64,并且还可以处理字节编码不使用所有字节的值,例如 [0x01, 0x02, 0x03]

【讨论】:

这会产生 2049,但正确的结果应该是 513。将 010 更改为 8 会产生当前答案。 010 是以 10 为底,0o10 是以 8 为底。虽然过于聪明,但它也很昂贵,需要一段时间才能看到发生了什么。 更正了答案,因此现在可以正确使用八进制表示。感谢您指出我错过了 o。 八进制?为什么是八进制,只是为了晦涩难懂? 8 不是更明智/更容易理解吗?唯一比较模糊的是八进制 ascii,如 Tar 中所见。 更新了答案以解释为什么使用八进制。 如果你必须通过所有这些来证明使用八进制的合理性,那可能是聪明的。最好的代码对普通读者来说是显而易见的。

以上是关于在 Swift 中将两个字节的 UInt8 数组转换为 UInt16的主要内容,如果未能解决你的问题,请参考以下文章

在Swift中将bytes / UInt8数组转换为Int

如何在 Swift 3 中将 [UInt8] 数组复制到 C 指针?

swift中自定义的结构体怎么转成data

在 Swift 中将字节数组转换为 UIImage

如何在 Swift 中将 Floats 的 ContiguousArray 转换为字节数组?

如何在 Swift 4 中将 UIImage 转换为字节数组