You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何即时转换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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:35:39