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

37
如何将一个由4个字节组成的数组转换为相应的整数?
let array: [UInt8] ==> let value : Int

例子:

输入:

\0\0\0\x0e

输出:

14

我在网上找到的一些代码无法正常工作:

let data = NSData(bytes: array, length: 4)
data.getBytes(&size, length: 4)
// the output to size is 184549376

@MartinR 输出是错误的,它是一个非常大的数字。 - Jerry
@MartinR 谢谢您回复我的其它帖子。但目前为止,它们都没有被真正解决。 - Jerry
7个回答

55

有两个问题:

  • Int在64位平台上是一个64位整数,而你的输入数据只有32位。
  • Int在当前所有Swift平台上都使用小端表示法,而你的输入是大端表示法。

话虽如此,以下内容可以运行:

let array : [UInt8] = [0, 0, 0, 0x0E]
var value : UInt32 = 0
let data = NSData(bytes: array, length: 4)
data.getBytes(&value, length: 4)
value = UInt32(bigEndian: value)

print(value) // 14

或者在 Swift 3 中使用 Data

let array : [UInt8] = [0, 0, 0, 0x0E]
let data = Data(bytes: array)
let value = UInt32(bigEndian: data.withUnsafeBytes { $0.pointee })

通过一些缓冲指针技巧,您可以避免中间复制到NSData对象(Swift 2):

let array : [UInt8] = [0, 0, 0, 0x0E]
var value = array.withUnsafeBufferPointer({ 
     UnsafePointer<UInt32>($0.baseAddress).memory
})
value = UInt32(bigEndian: value)

print(value) // 14

要查看此方法的 Swift 3 版本,请参见 ambientlight 的答案。


1
@HansBrende:在Swift 3中,NSData仍然有一个getBytes方法,只是它的覆盖类型Data没有。我已经添加了一个Data版本,另外ambientlight已经发布了另一种适用于Swift 3的解决方案。 - Martin R
1
@HansBrende:另请参阅https://dev59.com/zFoT5IYBdhLWcg3w6i23,以获取更一般的有关将数据转换为Swift数字类型和反向转换的处理方法。 - Martin R

16

在Swift 3中,现在有点啰嗦:

let array : [UInt8] = [0, 0, 0, 0x0E]
let bigEndianValue = array.withUnsafeBufferPointer {
         ($0.baseAddress!.withMemoryRebound(to: UInt32.self, capacity: 1) { $0 })
}.pointee
let value = UInt32(bigEndian: bigEndianValue)

这个方案在性能上如何?在数据复制和分配/保留周期方面是否存在任何开销?我想在我的服务器Swift代码中用[UInt8]替换NSMutableData。 - Antwan van Houdt

13

我认为Martin的答案比这个更好,但我仍然想发布我的。任何建议都将非常有帮助。

let array : [UInt8] = [0, 0, 0, 0x0E]
var value : Int = 0
for byte in array {
    value = value << 8
    value = value | Int(byte)
}
print(value) // 14

3
那么为什么不接受Martin的答案呢?在“赞同票”的数量下面有一个勾选按钮。它是一个勾选框,当你点击它时,它会变成绿色。 - smttsp
1
实际上,你的回答对我非常有帮助。使用被接受的方法时,我得到了错误的整数值,但是你的方法给了我正确的结果。也许我只是错误地实现了被接受的方法,但仍然感谢你的帮助。 - davis
这对于有符号数失败了。如何修改为有符号整数?例如:[0xFF,0xFF,0xFF,0xAB] - krishnakumarcn

12

这里有一些很好的答案,看到真是太好了^^ 但是如果你希望避免与Swift的C互操作API交互,那么我建议您看一下我的示例。 它对于所有数据类型大小也同样通用。请注意,MemoryLayout仅被用作安全检查。

代码:

public extension UnsignedInteger {
    init(_ bytes: [UInt8]) {
        precondition(bytes.count <= MemoryLayout<Self>.size)

        var value: UInt64 = 0

        for byte in bytes {
            value <<= 8
            value |= UInt64(byte)
        }

        self.init(value)
    }
}

示例用法:

let someBytes = [UInt8](repeating: 0x42, count: 2)
let someValue = UInt16(someBytes)

对于小端支持,您需要改为使用for byte in bytes.reversed()

let bytes = [UInt8](repeating: UInt8(255), count: 2)
var short: UInt16 = 0

// "add" our first unsinged byte
short |= UInt16(bytes[0])
// our short now looks like this in memory: 0b0000000011111111

// make room for the unsinged byte ;)
short <<= 8 
// our short now looks like this in memory: 0b1111111100000000

// "add" our last unsinged byte
short |= UInt16(bytes[1])
// our short now looks like this in memory: 0b1111111111111111

print(short == UInt16.max)

1
你能解释一下位运算或者提供一个解释的链接吗? - aleclarson
@aleclarson sure, - Kim Visscher

10

当您不知道字节数组(或您的Data大小)的大小时,接受的答案存在问题。

对于let array : [UInt8] = [0, 0, 0x23, 0xFF],它可以正常工作。

但是对于let array : [UInt8] = [0x23, 0xFF]无法正常工作
(因为它将被视为[0x23, 0xFF, 0, 0]

这就是为什么我喜欢@Jerry的位运算方法。

我已经制作了他代码片段的功能版本。

let data = Data(bytes: [0x23, 0xFF])
let decimalValue = data.reduce(0) { v, byte in
    return v << 8 | Int(byte)
}

这应该是被接受的答案。 - Eddy

6

更新至Swift 5,需要注意以下两点:

  • 由于[UInt8]在内存中是连续的一段区域,因此无需将其转换为Data,指针可以直接访问所有字节。

  • Int的字节顺序当前在所有苹果平台上都是小端,但在其他平台上不能保证。

假设我们要将[0, 0, 0, 0x0e]转换为14。(大端字节顺序)

let source: [UInt8] = [0, 0, 0, 0x0e]
let bigEndianUInt32 = source.withUnsafeBytes { $0.load(as: UInt32.self) }
let value = CFByteOrderGetCurrent() == CFByteOrder(CFByteOrderLittleEndian.rawValue)
    ? UInt32(bigEndian: bigEndianUInt32)
    : bigEndianUInt32
print(value) // 14

1
对于那些喜欢保守做法的人,这里提供一组从字节数组获取int值的方法。这适用于顺序处理包含各种数据类型的字节数组的情况。
/// Class which encapsulates a Swift byte array (an Array object with elements of type UInt8) and an
/// index into the array.
open class ByteArrayAndIndex {

   private var _byteArray : [UInt8]
   private var _arrayIndex = 0

   public init(_ byteArray : [UInt8]) {
      _byteArray = byteArray;
   }

   /// Property to provide read-only access to the current array index value.
   public var arrayIndex : Int {
      get { return _arrayIndex }
   }

   /// Property to calculate how many bytes are left in the byte array, i.e., from the index point
   /// to the end of the byte array.
   public var bytesLeft : Int {
      get { return _byteArray.count - _arrayIndex }
   }

   /// Method to get a single byte from the byte array.
   public func getUInt8() -> UInt8 {
      let returnValue = _byteArray[_arrayIndex]
      _arrayIndex += 1
      return returnValue
   }

   /// Method to get an Int16 from two bytes in the byte array (little-endian).
   public func getInt16() -> Int16 {
      return Int16(bitPattern: getUInt16())
   }

   /// Method to get a UInt16 from two bytes in the byte array (little-endian).
   public func getUInt16() -> UInt16 {
      let returnValue = UInt16(_byteArray[_arrayIndex]) |
                        UInt16(_byteArray[_arrayIndex + 1]) << 8
      _arrayIndex += 2
      return returnValue
   }

   /// Method to get a UInt from three bytes in the byte array (little-endian).
   public func getUInt24() -> UInt {
      let returnValue = UInt(_byteArray[_arrayIndex]) |
                        UInt(_byteArray[_arrayIndex + 1]) << 8 |
                        UInt(_byteArray[_arrayIndex + 2]) << 16
      _arrayIndex += 3
      return returnValue
   }

   /// Method to get an Int32 from four bytes in the byte array (little-endian).
   public func getInt32() -> Int32 {
      return Int32(bitPattern: getUInt32())
   }

   /// Method to get a UInt32 from four bytes in the byte array (little-endian).
   public func getUInt32() -> UInt32 {
      let returnValue = UInt32(_byteArray[_arrayIndex]) |
                        UInt32(_byteArray[_arrayIndex + 1]) << 8 |
                        UInt32(_byteArray[_arrayIndex + 2]) << 16 |
                        UInt32(_byteArray[_arrayIndex + 3]) << 24
      _arrayIndex += 4
      return returnValue
   }

   /// Method to get an Int64 from eight bytes in the byte array (little-endian).
   public func getInt64() -> Int64 {
      return Int64(bitPattern: getUInt64())
   }

   /// Method to get a UInt64 from eight bytes in the byte array (little-endian).
   public func getUInt64() -> UInt64 {
      let returnValue = UInt64(_byteArray[_arrayIndex]) |
                        UInt64(_byteArray[_arrayIndex + 1]) << 8 |
                        UInt64(_byteArray[_arrayIndex + 2]) << 16 |
                        UInt64(_byteArray[_arrayIndex + 3]) << 24 |
                        UInt64(_byteArray[_arrayIndex + 4]) << 32 |
                        UInt64(_byteArray[_arrayIndex + 5]) << 40 |
                        UInt64(_byteArray[_arrayIndex + 6]) << 48 |
                        UInt64(_byteArray[_arrayIndex + 7]) << 56
      _arrayIndex += 8
      return returnValue
   }
}

这是一个从包含提取字符串和其他类型数据的方法的大型类中提取出来的片段。也可参见此处:https://dev59.com/F10b5IYBdhLWcg3wEdox#41592206

我曾尝试使用这个类,但在Xcode 13中运行函数public func getUInt64() -> UInt64时遇到了Swift编译器错误。 编译器无法在合理的时间内对此表达式进行类型检查;请尝试将表达式分解为不同的子表达式 - Pradip Sutariya

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