[解決済み] Swiftでファイル/URLを一行ずつ読み込む
質問
で指定されたファイルを読み込もうとしています。
NSURL
で指定されたファイルを読み込み、それを改行文字で区切られた配列にロードします。
\n
.
今までのやり方はこんな感じです。
var possList: NSString? = NSString.stringWithContentsOfURL(filePath.URL) as? NSString
if var list = possList {
list = list.componentsSeparatedByString("\n") as NSString[]
return list
}
else {
//return empty list
}
いくつかの理由から、私はこれにはあまり満足していません。1 つは、数キロバイトから数百メガバイトのサイズのファイルを扱っていることです。ご想像のとおり、これほど大きな文字列を扱うのは遅くて扱いにくいものです。第二に、これは実行時に UI をフリーズさせるので、これもよくありません。
このコードを別のスレッドで実行することを検討しましたが、それには問題があり、さらに、巨大な文字列を処理する問題はまだ解決されていません。
私がやりたいことは、次の疑似コードのようなものです。
var aStreamReader = new StreamReader(from_file_or_url)
while aStreamReader.hasNextLine == true {
currentline = aStreamReader.nextLine()
list.addItem(currentline)
}
Swiftでこれを実現するにはどうしたらいいでしょうか?
読み込んでいるファイルについて少しメモ。
すべてのファイルは、短い (<255 chars) 文字列で構成され、それぞれを
\n
または
\r\n
. ファイルの長さは、100行から5,000万行以上まであります。また、欧文文字やアクセント記号付きの文字が含まれている場合があります。
どのように解決するのですか?
(このコードは現在Swift 2.2/Xcode 7.3用です。古いバージョンは、誰かがそれを必要とする場合、編集履歴で見つけることができます。Swift 3用の更新されたバージョンは最後に提供されています)。
次のSwiftコードは NSFileHandleからデータを一行ずつ読み取る方法は? . これは、チャンクでファイルから読み取り、完全な行を文字列に変換します。
デフォルトの行区切り文字 (
\n
)、文字列エンコーディング(UTF-8)、チャンクサイズ(4096)
はオプションのパラメータで設定することができます。
class StreamReader {
let encoding : UInt
let chunkSize : Int
var fileHandle : NSFileHandle!
let buffer : NSMutableData!
let delimData : NSData!
var atEof : Bool = false
init?(path: String, delimiter: String = "\n", encoding : UInt = NSUTF8StringEncoding, chunkSize : Int = 4096) {
self.chunkSize = chunkSize
self.encoding = encoding
if let fileHandle = NSFileHandle(forReadingAtPath: path),
delimData = delimiter.dataUsingEncoding(encoding),
buffer = NSMutableData(capacity: chunkSize)
{
self.fileHandle = fileHandle
self.delimData = delimData
self.buffer = buffer
} else {
self.fileHandle = nil
self.delimData = nil
self.buffer = nil
return nil
}
}
deinit {
self.close()
}
/// Return next line, or nil on EOF.
func nextLine() -> String? {
precondition(fileHandle != nil, "Attempt to read from closed file")
if atEof {
return nil
}
// Read data chunks from file until a line delimiter is found:
var range = buffer.rangeOfData(delimData, options: [], range: NSMakeRange(0, buffer.length))
while range.location == NSNotFound {
let tmpData = fileHandle.readDataOfLength(chunkSize)
if tmpData.length == 0 {
// EOF or read error.
atEof = true
if buffer.length > 0 {
// Buffer contains last line in file (not terminated by delimiter).
let line = NSString(data: buffer, encoding: encoding)
buffer.length = 0
return line as String?
}
// No more lines.
return nil
}
buffer.appendData(tmpData)
range = buffer.rangeOfData(delimData, options: [], range: NSMakeRange(0, buffer.length))
}
// Convert complete line (excluding the delimiter) to a string:
let line = NSString(data: buffer.subdataWithRange(NSMakeRange(0, range.location)),
encoding: encoding)
// Remove line (and the delimiter) from the buffer:
buffer.replaceBytesInRange(NSMakeRange(0, range.location + range.length), withBytes: nil, length: 0)
return line as String?
}
/// Start reading from the beginning of file.
func rewind() -> Void {
fileHandle.seekToFileOffset(0)
buffer.length = 0
atEof = false
}
/// Close the underlying file. No reading must be done after calling this method.
func close() -> Void {
fileHandle?.closeFile()
fileHandle = nil
}
}
使用方法
if let aStreamReader = StreamReader(path: "/path/to/file") {
defer {
aStreamReader.close()
}
while let line = aStreamReader.nextLine() {
print(line)
}
}
for-inループでreaderを使うこともできます。
for line in aStreamReader {
print(line)
}
を実装することで
SequenceType
プロトコルを実装することにより(比較
http://robots.thoughtbot.com/swift-sequences
):
extension StreamReader : SequenceType {
func generate() -> AnyGenerator<String> {
return AnyGenerator {
return self.nextLine()
}
}
}
Swift 3/Xcode 8 beta 6に対応したアップデートを行いました。
また、"modernized" で
使用
guard
と新しい
Data
という値型があります。
class StreamReader {
let encoding : String.Encoding
let chunkSize : Int
var fileHandle : FileHandle!
let delimData : Data
var buffer : Data
var atEof : Bool
init?(path: String, delimiter: String = "\n", encoding: String.Encoding = .utf8,
chunkSize: Int = 4096) {
guard let fileHandle = FileHandle(forReadingAtPath: path),
let delimData = delimiter.data(using: encoding) else {
return nil
}
self.encoding = encoding
self.chunkSize = chunkSize
self.fileHandle = fileHandle
self.delimData = delimData
self.buffer = Data(capacity: chunkSize)
self.atEof = false
}
deinit {
self.close()
}
/// Return next line, or nil on EOF.
func nextLine() -> String? {
precondition(fileHandle != nil, "Attempt to read from closed file")
// Read data chunks from file until a line delimiter is found:
while !atEof {
if let range = buffer.range(of: delimData) {
// Convert complete line (excluding the delimiter) to a string:
let line = String(data: buffer.subdata(in: 0..<range.lowerBound), encoding: encoding)
// Remove line (and the delimiter) from the buffer:
buffer.removeSubrange(0..<range.upperBound)
return line
}
let tmpData = fileHandle.readData(ofLength: chunkSize)
if tmpData.count > 0 {
buffer.append(tmpData)
} else {
// EOF or read error.
atEof = true
if buffer.count > 0 {
// Buffer contains last line in file (not terminated by delimiter).
let line = String(data: buffer as Data, encoding: encoding)
buffer.count = 0
return line
}
}
}
return nil
}
/// Start reading from the beginning of file.
func rewind() -> Void {
fileHandle.seek(toFileOffset: 0)
buffer.count = 0
atEof = false
}
/// Close the underlying file. No reading must be done after calling this method.
func close() -> Void {
fileHandle?.closeFile()
fileHandle = nil
}
}
extension StreamReader : Sequence {
func makeIterator() -> AnyIterator<String> {
return AnyIterator {
return self.nextLine()
}
}
}
関連
-
[解決済み] あるJavaScriptファイルを他のJavaScriptファイルにインクルードするにはどうすればよいですか?
-
[解決済み] JavaでInputStreamを読み込んでStringに変換するにはどうすればよいですか?
-
[解決済み] ファイルのコピー方法について教えてください。
-
[解決済み] ファイルへの追記はどのように行うのですか?
-
[解決済み] Gitで1つのファイルの作業コピーの変更を元に戻す?
-
[解決済み] ファイルの内容からJavaの文字列を作成するにはどうすればよいですか?
-
[解決済み] ファイルの作成日時、変更日時を取得する方法
-
[解決済み] Swiftで#pragmaマーク?
-
[解決済み] MacからParallels Windowsのローカルホストにアクセスする【非公開
-
[解決済み] OS X 10.6.7で22番ポートを開くには?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み] 文字列の長さを取得する
-
[解決済み】文字列から最後の文字を削除する。Swift言語
-
[解決済み] Objective-C。ファイルを一行ずつ読む
-
[解決済み] brew サービスで動作している Postgres サーバーに接続できない
-
[解決済み] OS X で $PATH 変数の現在の値を見るにはどうしたらいいですか?
-
[解決済み] NSTextFieldから値を取得する
-
[解決済み] brewでgoをインストールし、gotourを実行する。
-
[解決済み] OSXでコマンドラインからGUI Emacsを起動するには?
-
[解決済み] エラーです。ARM プロセッサの Homebrew では Intel のデフォルトプレフィックス (/usr/local) にインストールできません。
-
[解決済み] Vagrant box がリモートカタログに見つからない、またはアクセスできない - curl のバージョンに互換性がない