【问题标题】:Convert a two byte UInt8 array to a UInt16 in Swift在 Swift 中将两个字节的 UInt8 数组转换为 UInt16
【发布时间】: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]

标签: swift


【解决方案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 中所见。
      • 更新了答案以解释为什么使用八进制。
      • 如果你必须通过所有这些来证明使用八进制的合理性,那可能是聪明的。最好的代码对普通读者来说是显而易见的。
      猜你喜欢
      • 2017-11-05
      • 2020-06-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-07-24
      • 2022-01-11
      • 1970-01-01
      相关资源
      最近更新 更多