如何即时转换url.lines返回的WindowsCP1252编码行至UTF8?
正确处理Windows CP1252编码的远程CSV逐行读取问题
你的问题出在解码顺序完全搞反了:url.lines 默认会把远程数据当作UTF-8编码来解码成String,但你的文件实际是Windows CP1252编码,这一步已经把特殊字符损坏了,后续再转成CP1252的Data再转回UTF-8自然只会得到问号或乱码。
要解决这个问题,你需要跳过url.lines的默认解码,直接获取原始字节流,再用CP1252编码来解码成正确的String,之后再逐行处理。以下是可行的实现:
基础数据流处理方案
import Foundation let url = URL(string: "https://foo.bar/data.csv")! // 创建URLSession数据流任务 let task = URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data, error == nil else { print("请求失败: \(error?.localizedDescription ?? "未知错误")") return } // 确认系统支持Windows CP1252编码解码器 guard let decoder = String.Encoding.windowsCP1252.stringDecoder else { print("当前环境不支持Windows CP1252编码") return } var processedData = data var remainingPartialLine = "" var decodedLines: [String] = [] // 流式解码原始字节数据,拆分完整行 while !processedData.isEmpty { let (decodedChunk, consumedBytes, isComplete) = decoder.decode(processedData) processedData = processedData.dropFirst(consumedBytes) if let chunk = decodedChunk { let fullContent = remainingPartialLine + chunk let splitLines = fullContent.components(separatedBy: .newlines) // 除最后一个元素外,都是完整行 decodedLines.append(contentsOf: splitLines.dropLast()) // 最后一个元素可能是不完整的行,留到下一轮处理 remainingPartialLine = splitLines.last ?? "" } if isComplete { break } } // 处理最后剩余的不完整行 if !remainingPartialLine.isEmpty { decodedLines.append(remainingPartialLine) } // 遍历处理每一行 for line in decodedLines { print(line) // 这里添加你的业务逻辑 } } task.resume()
异步/await风格实现(贴合url.lines使用习惯)
如果想保持和url.lines一致的异步迭代体验,可以封装成自定义异步序列:
import Foundation struct CP1252LineSequence: AsyncSequence { typealias Element = String private let url: URL init(url: URL) { self.url = url } func makeAsyncIterator() -> AsyncIterator { AsyncIterator(url: url) } struct AsyncIterator: AsyncIteratorProtocol { private let url: URL private var stream: AsyncThrowingStream<String, Error>.Iterator? init(url: URL) { self.url = url self.stream = Self.createLineStream(for: url).makeAsyncIterator() } private static func createLineStream(for url: URL) -> AsyncThrowingStream<String, Error> { AsyncThrowingStream { continuation in let task = URLSession.shared.dataTask(with: url) { data, response, error in guard let data = data else { continuation.finish(throwing: error ?? NSError(domain: "CP1252LineSequence", code: -1, userInfo: [NSLocalizedDescriptionKey: "获取数据失败"])) return } guard let decoder = String.Encoding.windowsCP1252.stringDecoder else { continuation.finish(throwing: NSError(domain: "CP1252LineSequence", code: -2, userInfo: [NSLocalizedDescriptionKey: "不支持Windows CP1252编码"])) return } var processedData = data var remainingPartialLine = "" while !processedData.isEmpty { let (decodedChunk, consumedBytes, isComplete) = decoder.decode(processedData) processedData = processedData.dropFirst(consumedBytes) if let chunk = decodedChunk { let fullContent = remainingPartialLine + chunk let splitLines = fullContent.components(separatedBy: .newlines) // 逐个输出完整行 for line in splitLines.dropLast() { continuation.yield(line) } remainingPartialLine = splitLines.last ?? "" } if isComplete { break } } // 输出最后一行 if !remainingPartialLine.isEmpty { continuation.yield(remainingPartialLine) } continuation.finish() } task.resume() } } mutating func next() async throws -> String? { try await stream?.next() } } } // 使用示例 Task { let url = URL(string: "https://foo.bar/data.csv")! do { for try await line in CP1252LineSequence(url: url) { print(line) // 处理每一行数据 } } catch { print("处理错误: \(error)") } }
核心逻辑总结:先获取原始字节数据,用正确的CP1252编码解码,再拆分完整行,避免了url.lines默认UTF-8解码导致的字符损坏问题。
内容的提问来源于stack exchange,提问作者parapote
相关产品推荐
相关产品推荐

