(Lưu ý: Tôi đang sử dụng Swift 3.0.1 trên Xcode 8.2.1 với macOS Sierra 10.12.3)
Tất cả các câu trả lời tôi thấy ở đây đều bỏ sót rằng anh ta có thể đang tìm kiếm LF hoặc CRLF. Nếu mọi thứ diễn ra tốt đẹp, anh ấy / anh ấy có thể đối sánh trên LF và kiểm tra chuỗi trả về để tìm thêm CR ở cuối. Nhưng truy vấn chung liên quan đến nhiều chuỗi tìm kiếm. Nói cách khác, dấu phân tách cần phải là a Set<String>
, trong đó tập hợp không trống hoặc không chứa chuỗi trống, thay vì một chuỗi đơn.
Trong lần thử đầu tiên vào năm ngoái, tôi đã cố gắng làm "điều đúng đắn" và tìm kiếm một bộ chuỗi chung. Nó quá khó; bạn cần một trình phân tích cú pháp và máy trạng thái hoàn chỉnh và như vậy. Tôi đã từ bỏ nó và dự án đó là một phần của nó.
Bây giờ tôi đang thực hiện lại dự án và đối mặt với thách thức tương tự. Bây giờ tôi sẽ tìm kiếm mã cứng trên CR và LF. Tôi không nghĩ rằng bất cứ ai sẽ cần phải tìm kiếm trên hai ký tự nửa độc lập và nửa phụ thuộc như thế này ngoài phân tích cú pháp CR / LF.
Tôi đang sử dụng các phương pháp tìm kiếm được cung cấp bởi Data
vì vậy tôi không thực hiện mã hóa chuỗi và nội dung ở đây. Chỉ xử lý nhị phân thô. Chỉ cần giả sử tôi có bộ siêu ASCII, như ISO Latinh-1 hoặc UTF-8, tại đây. Bạn có thể xử lý mã hóa chuỗi ở lớp tiếp theo cao hơn và bạn quyết định xem CR / LF có đính kèm điểm mã phụ vẫn được tính là CR hay LF hay không.
Thuật toán: chỉ cần tiếp tục tìm kiếm CR tiếp theo và LF tiếp theo từ phần bù byte hiện tại của bạn.
- Nếu cả hai đều không được tìm thấy, thì hãy coi chuỗi dữ liệu tiếp theo là từ phần bù hiện tại đến phần cuối của dữ liệu. Lưu ý rằng độ dài của dấu chấm dứt là 0. Đánh dấu đây là phần cuối của vòng lặp đọc của bạn.
- Nếu một LF được tìm thấy trước hoặc chỉ một LF được tìm thấy, hãy coi chuỗi dữ liệu tiếp theo là từ giá trị bù hiện tại đến LF. Lưu ý rằng độ dài của đầu cuối là 1. Di chuyển phần bù đến sau LF.
- Nếu chỉ tìm thấy một CR, hãy làm như trường hợp LF (chỉ với một giá trị byte khác).
- Nếu không, chúng ta có CR theo sau là LF.
- Nếu hai phần tử liền nhau, thì xử lý giống như trường hợp LF, ngoại trừ độ dài của phần tử cuối sẽ là 2.
- Nếu có một byte giữa chúng và byte đã nói cũng là CR, thì chúng tôi nhận được thông báo "Nhà phát triển Windows đã viết một tệp nhị phân \ r \ n khi ở chế độ văn bản, gây ra sự cố \ r \ r \ n". Cũng xử lý nó giống như trường hợp LF, ngoại trừ độ dài của đầu cuối sẽ là 3.
- Nếu không, CR và LF không được kết nối và xử lý như trường hợp just-CR.
Đây là một số mã cho điều đó:
struct DataInternetLineIterator: IteratorProtocol {
typealias LineLocation = (offset: Int, length: Int, terminatorLength: Int)
static let cr: UInt8 = 13
static let crData = Data(repeating: cr, count: 1)
static let lf: UInt8 = 10
static let lfData = Data(repeating: lf, count: 1)
let data: Data
private var lineStartOffset: Int = 0
init(data: Data) {
self.data = data
}
mutating func next() -> LineLocation? {
guard self.data.count - self.lineStartOffset > 0 else { return nil }
let nextCR = self.data.range(of: DataInternetLineIterator.crData, options: [], in: lineStartOffset..<self.data.count)?.lowerBound
let nextLF = self.data.range(of: DataInternetLineIterator.lfData, options: [], in: lineStartOffset..<self.data.count)?.lowerBound
var location: LineLocation = (self.lineStartOffset, -self.lineStartOffset, 0)
let lineEndOffset: Int
switch (nextCR, nextLF) {
case (nil, nil):
lineEndOffset = self.data.count
case (nil, let offsetLf):
lineEndOffset = offsetLf!
location.terminatorLength = 1
case (let offsetCr, nil):
lineEndOffset = offsetCr!
location.terminatorLength = 1
default:
lineEndOffset = min(nextLF!, nextCR!)
if nextLF! < nextCR! {
location.terminatorLength = 1
} else {
switch nextLF! - nextCR! {
case 2 where self.data[nextCR! + 1] == DataInternetLineIterator.cr:
location.terminatorLength += 1
fallthrough
case 1:
location.terminatorLength += 1
fallthrough
default:
location.terminatorLength += 1
}
}
}
self.lineStartOffset = lineEndOffset + location.terminatorLength
location.length += self.lineStartOffset
return location
}
}
Tất nhiên, nếu bạn có một Data
khối có độ dài ít nhất là một phần đáng kể của gigabyte, bạn sẽ nhận được một lần truy cập bất cứ khi nào không còn CR hoặc LF tồn tại từ phần bù byte hiện tại; luôn tìm kiếm không kết quả cho đến cuối trong mỗi lần lặp lại. Đọc dữ liệu theo từng phần sẽ giúp:
struct DataBlockIterator: IteratorProtocol {
let data: Data
private(set) var blockOffset = 0
private(set) var bytesRemaining: Int
let blockSize: Int
init(data: Data, blockSize: Int) {
precondition(blockSize > 0)
self.data = data
self.bytesRemaining = data.count
self.blockSize = blockSize
}
mutating func next() -> Data? {
guard bytesRemaining > 0 else { return nil }
defer { blockOffset += blockSize ; bytesRemaining -= blockSize }
return data.subdata(in: blockOffset..<(blockOffset + min(bytesRemaining, blockSize)))
}
}
Bạn phải tự mình trộn những ý tưởng này với nhau, vì tôi chưa làm được. Xem xét:
- Tất nhiên, bạn phải xem xét các dòng hoàn toàn được chứa trong một đoạn.
- Nhưng bạn phải xử lý khi các đầu của một dòng nằm trong các đoạn liền kề.
- Hoặc khi các điểm cuối có ít nhất một đoạn giữa chúng
- Sự phức tạp lớn là khi dòng kết thúc bằng một chuỗi nhiều byte, nhưng chuỗi này lại xếp hai phần! (Một dòng kết thúc bằng CR cũng là byte cuối cùng trong đoạn mã là một trường hợp tương đương, vì bạn cần đọc đoạn tiếp theo để xem chỉ-CR của bạn thực sự là CRLF hay CR-CRLF. Có những điều tai quái tương tự khi đoạn kết thúc bằng CR-CR.)
- Và bạn cần xử lý khi không còn đầu cuối nào nữa từ phần bù hiện tại của bạn, nhưng phần cuối của dữ liệu nằm ở phần sau.
Chúc may mắn!