如何解析含字符串与浮点型的十六进制数据?二进制文件解析异常求助
嘿,我来帮你搞定这个二进制解析的问题!
问题根源分析
你直接用.ascii把整个二进制数据转成String肯定行不通——因为你的文件里不只有ASCII格式的地名字符串,还有浮点坐标的二进制原始数据。这些浮点值的二进制编码根本不是ASCII字符,强行转成String只会得到乱码,甚至直接解析失败。核心问题是:不能把包含非ASCII二进制内容的Data当成纯字符串处理,得按照二进制文件的预设格式约定拆分数据,分别处理字符串和浮点部分。
解析混合类型(字符串+浮点)二进制数据的步骤
首先你得明确你的二进制文件的格式规则——比如:
- 地名字符串是固定长度?还是以空字节
\0作为结束标记? - 坐标的浮点型是32位(Float)还是64位(Double)?字节序是小端(Little Endian,大部分设备默认)还是大端(Big Endian)?
我先拿最常见的格式场景举例(地名是ASCII字符串+空字节结尾,后续跟着两个64位小端Double:纬度、经度),给你一步步写解析逻辑:
1. 先完善你的InputStream读取代码
你提供的Data初始化代码有点不完整,我帮你补全,确保能正确读取整个文件内容:
extension Data { init(reading input: InputStream) { self.init() input.open() defer { input.close() } // 确保流最终关闭 let bufferSize = 1024 let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize) defer { buffer.deallocate() } // 确保内存释放 while input.hasBytesAvailable { let readCount = input.read(buffer, maxLength: bufferSize) if readCount < 0 { // 处理读取错误,比如权限问题或文件损坏 break } else if readCount == 0 { // 读取完毕 break } else { self.append(buffer, count: readCount) } } } }
2. 核心解析逻辑
我们需要直接访问Data的底层二进制内存,按照格式约定拆分解析:
示例代码(对应上述假设格式)
// 1. 先读取整个二进制文件到Data guard let inputStream = InputStream(fileAtPath: "/path/to/your/target/file.bin") else { fatalError("无法打开目标文件,请检查路径和权限") } let fileData = Data(reading: inputStream) // 2. 开始解析混合数据 fileData.withUnsafeBytes { rawBufferPtr in guard let baseAddr = rawBufferPtr.baseAddress else { fatalError("数据为空,无法解析") } let uint8Ptr = baseAddr.assumingMemoryBound(to: UInt8.self) var currentOffset = 0 let totalBytes = fileData.count // 解析ASCII地名:找到第一个空字节的位置 var nameBytes: [UInt8] = [] while currentOffset < totalBytes { let currentByte = uint8Ptr[currentOffset] if currentByte == 0 { currentOffset += 1 // 跳过空字节分隔符 break } nameBytes.append(currentByte) currentOffset += 1 } guard let placeName = String(bytes: nameBytes, encoding: .ascii) else { fatalError("地名解析失败,可能不是ASCII编码") } print("解析得到地名:\(placeName)") // 解析浮点坐标:假设是64位Double(小端字节序) let doubleByteCount = MemoryLayout<Double>.stride guard currentOffset + doubleByteCount * 2 <= totalBytes else { fatalError("剩余数据长度不足,无法解析完整坐标") } // 解析纬度 let latitudePtr = uint8Ptr + currentOffset let latitude = latitudePtr.withMemoryRebound(to: Double.self, capacity: 1) { $0.pointee } currentOffset += doubleByteCount // 解析经度 let longitudePtr = uint8Ptr + currentOffset let longitude = longitudePtr.withMemoryRebound(to: Double.self, capacity: 1) { $0.pointee } currentOffset += doubleByteCount print("解析得到坐标:纬度 \(latitude),经度 \(longitude)") }
针对不同格式的调整点
- 如果坐标是32位Float:把代码里的
Double换成Float,MemoryLayout<Double>换成MemoryLayout<Float>即可。 - 如果字节序是大端:需要手动转换字节序,比如
let latitude = Double(bigEndian: latitudePtr.withMemoryRebound(...) { $0.pointee })。 - 如果地名是固定长度:不用找空字节,直接截取对应长度的子数据转成String,比如:
let nameByteRange = 0..<20 // 假设地名固定20字节 let nameBytes = fileData.subdata(in: nameByteRange) guard let placeName = String(bytes: nameBytes, encoding: .ascii)?.trimmingCharacters(in: .controlCharacters) else { fatalError("地名解析失败") } currentOffset = 20
再啰嗦一句为什么直接转ASCII不行
举个直观例子:Double值31.23的小端二进制表示是0x9A 0x99 0x99 0x99 0x99 0x19 0x3F 0x40,这些字节对应的ASCII字符要么是不可见的控制符,要么是乱码符号,所以把整个包含这些字节的Data直接转成String,肯定会得到一堆毫无意义的内容,甚至直接解析失败。必须拆分数据,分别处理字符串和二进制数值部分。
内容的提问来源于stack exchange,提问作者yerpy
相关产品推荐
相关产品推荐

