Visual Basic 初級講座 |
Visual Basic 中学校 > 初級講座 >
第30回 ファイル処理
テキストファイルから文字列を読み込んだり、テキストファイルに文字列を書き込む方法を説明します。これにより、アプリケーションを終了してもデータを保存しておく機能をプログラムできるようになります。
この回の要約 ・テキストファイルから読み込みにはStreamReaderクラスを使う。 Dim
Reader As New IO.StreamReader("C:\VBTest.txt") ・テキストファイルに書き込むにはStreamWriterクラスを使う。 Dim Writer As New
IO.StreamWriter("C:\VBTest.txt") ・テキストファイルを正しく読み書きするためにはShift-JISやUTF-8などの文字コードの指定が必要な場合がある。 ・CSVファイルを読み込むにはReadLineメソッドとSplitメソッドを使用する。 |
ファイルの処理はさまざまなアプリケーションで必要となりますが、今回ここで取り上げるのはテキストファイルの書き込みと読み込みに関するトピックスです。
テキストファイルとは最も基本的なファイル形式の1つで、Windowsに付属のメモ帳で読み込んだり書き込んだりできる形式のファイルです。通常の拡張子は「txt」です。ただし、拡張子は自由につけられるので「txt」以外の拡張子のテキストファイルも無数に存在します。
iniファイルや、xmlファイル、htmlファイル、vbファイル、csファイルなどは広い意味でテキストファイルの一種です。試しにメモ帳で開いてみれば正常に読み書きできることが確認できます。ですから今回説明する内容をそのまま適用してこれらのファイルを読み書きすることもできます。ただし、これらのファイルは独特の構造を持っているので、それらの構造を無視した書き込みを行った場合テキストファイルとしては有効なファイルでも、iniファイルや、xmlファイルとしては無効なファイルとなってしまいますので注意が必要です。(しかしながら、読み込みだけならばこれらの構造を破壊してしまうことはありえません。)
また、iniファイルやxmlファイルには今回説明する方法以外のもっとふさわしい読み書きの方法が用意されています。
発展学習 - いろいろなファイル 発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。 ファイルを分類するときにテキストファイルとバイナリファイルの2種類に分類する方法がよく使われます。テキストファイルはメモ帳などのテキストエディタで開いたときに文字化けなどしないでそのまま内容が判読できるファイルです。テキストファイルでないすべてのファイルをバイナリファイルと呼びます。 この意味で考えると一般的に拡張子がini, xml, html ,vb, csなどとついている多くのファイルはテキストファイルであることになります。 今回解説対象としている「テキストファイル」とはこの広い意味でのテキストファイルです。 |
それでは、早速テキストファイルを読み込む処理を書いて見ましょう。単にテキストファイルを読み込むだけならとても簡単です。次のプログラムはテキストファイルを読み込んでテキストボックスに表示します。
対象のテキストボックはたくさんのテキストを表示できるようにMultiLineプロパティをTrueに、また、ScrollBarsプロパティをBothに設定してください。
Dim
Reader As
New IO.StreamReader("C:\Windows\System32\eula.txt") TextBox1.Text = Reader.ReadToEnd Reader.Close() |
■リスト1:ファイルの読み込み
ここでは、私のパソコンの中にあった適当なテキストファイルを対象としていますが、これはもちろんみんさんのお好みのテキストファイルを指定していただいて結構です。
細かい説明は後にしてとりあえず実行してみます。実行すると、確かにテキストファイルの内容らしきものが表示されますが、実はこの例ではうまく読み込める場合と読み込めない場合があります。これはファイルによって異なります。うまく読み込めないファイルの場合次のように表示されます。
■画像1:文字化けした場合
うまく読み込めない場合はプログラムを次のように修正して試してみてください。
Dim
Reader As
New IO.StreamReader("C:\Windows\System32\eula.txt",
System.Text.Encoding.GetEncoding("Shift-JIS")) TextBox1.Text = Reader.ReadToEnd Reader.Close() |
■リスト2:ファイルの読み込み
これでうまく読み込めるはずです。ただし、最初の例でうまく読み込めている場合はこの例のように修正すると逆に読み込めなくなります。
■画像2:正常に読み込めた場合
では、コードの説明とこの読めたり読めなかったりする現象の説明をしましょう。
まず、ファイルに対して処理を行う場合は読み込みであれ書き込みであれ開いてから閉じると手順が必要です。このことはファイル以外でもデータベースなど多くの処理で共通ですのでよく覚えて置いてください。
今回のコードではStreamReaderクラス(読み方:StreamReader = ストリームリーダー)を使ってファイルを読み込んでいますが、このクラスのインスタンスを作成する処理が「ファイルを開く」という操作に該当します。
StreamReaderクラスは複数のコンストラクタを備えていますが、最も簡単なのがリスト1で紹介した単にファイル名を指定する方法です。これだけでこのファイルを開くことができて、以降はこの変数(Reader)を利用してファイルに対する処理を行うことになります。
リスト2ではファイル名のほかに第2引数で「文字エンコーディング」を指定しています。「文字エンコーディング」については別の機会に詳しく取り上げたいと思いますが、指し当たっては文字コードの指定だと思っていただければ間違いないです。人間の目で見れば同じ文字でもコンピュータは違う形式で保存していることがあるのです。これが文字コードです。今回ファイルが読めたり読めなかったりしたのはこの文字コードが原因です。正しい文字コードを指定していないと正しく読み込めないのです。
文字コードにはShift-JIS(読み方:Shift-JIS = シフトジス)やUnicode(読み方:Unicode = ユニコード)などがあり、ファイルを作った人がどの文字コードを利用するか指定してます。VBでは標準的にUnicodeを使用しますのでUnicodeで記述されたファイルなら問題なく読み込めます。ところがShift-JISで記述されたファイルはそのままではうまく読み込めません。そこで自分で文字コードを指定するわけです。
System.Text.Encoding.GetEncoding("Shift-JIS") という記述が文字コードShift-JISを指定している部分です。この書き方はファイル処理ではよく登場するので覚えておくと良いでしょう。
なお、UTF-8など世界で多く使われている文字エンコーディングはあらかじめ専用のクラスが用意されているので、System.Text.Encoding.UTF8 のような指定方法が可能です。
ファイルを開く部分で説明が長くなってしまいましたが、ファイルを開いたら次は主目的である読み込みを行います。読み込み方によって書き方は異なりますが、ここではファイルの内容をすべて読み込むReadToEndメソッド(読み方:ReadToEnd = リードトゥーエンド)を使用しています。
最後にCloseメソッド(読み方:Close = クローズ)を使ってファイルを閉じます。
発展学習 - 文字コードの判別と設定 発展学習では意欲的な方のために現段階では特に理解する必要はない項目を解説します。 あるテキストファイルがどのような文字コードで記述されているか調べるにはソフトを使用します。たとえば、サクラエディタというソフトでテキストファイルを開いた場合、[ファイル]メニューの[開き直す]をポイントするとそのファイルの文字コードがすぐにわかります。 また、UTF-8形式のテキストファイルをShift-JIS形式に変更したり、その逆を行うにはサクラエディタで対象のファイルを開いた後で[名前を付けて保存]を行います。ファイル名を指定する画面で文字コードを指定することができます。 この便利なサクラエディタはこのほかにも様々な便利機能が搭載されている上にフリーウェアです。私の愛用しているソフトの1つです。 サクラエディタは次のサイトからダウンロードできます。V2(Unicode版)の最新版をお勧めします。 |
ただ単にテキストファイルの内容を表示したいというのであればReadToEndメソッドで十分ですがそうでない場合もあります。
これには表示させるのではなくプログラムで利用する場合などが挙げられます。たとえば、テキストファイルの1行目をフォームのタイトルに設定し、テキストファイルの2行目を変数UserNameにセットする場合を考えて見ましょう。ReadToEndメソッドでは不十分なことは明らかです。
きめ細かいニーズに対応するためにStreamReaderクラスではReadToEndメソッドも含めていくつかののメソッドが用意されています。
メソッド | 読み方 | 機能 |
Peek | ピーク | 次の1文字を読み込む。 |
Read | リード | 次の1文字を読み込んでカーソルを進める。複数文字読み込む機能もある。 |
ReadLine | リードライン | 次の1行を読み込んでカーソルを進める。 |
ReadToEnd | リードトゥーエンド | 最後まですべて読み込む。 |
■表1
他にもReadBlockメソッド(読み方:ReadBock = リードブロック)がありますが、需要が少ないため初級講座では紹介しません。
ReadメソッドとReadLineメソッドはそれぞれ次の1文字、次の1行を読み込むメソッドですがこれらのメソッドを使用すると自動的に読み込み点が次へ移動します。つまりReadメソッドを2回呼び出すと次の文字と次の次の文字が読み込めるし、ReadLineメソッドを2回使用すると次の行とさらにその次の行を読みことができるわけです。
このことを利用すると、次のように書くとテキストファイルの内容を1行ずつ取り出して最後まで読み込むことができます。この例はVB2005の例です。1行ずつリストボックスに表示するようにしていますから、何かの一覧をテキストファイルに保存しているときなどに便利に使用できます。VB.NET2002およびVB.NET2003の場合はすぐ次で説明します。
Dim
Reader As
New IO.StreamReader("C:\Test.txt",
System.Text.Encoding.GetEncoding("Shift-JIS"))
Do
Until
Reader.EndOfStream Reader.Close() |
■リスト3
この例ではループの終了条件にEndOfStreamプロパティ(読み方:EndOfStream = エンドオブストリーム)を使っています。ReadLineメソッドは自動的に次の行、次の行と読み込んでいって最後に到達するとEndOfStreamプロパティがTrueになります。まだ続きがあるうちはEndOfStreamプロパティはFalseです。
とことが、このEndOfStreamプロパティはVB2005から追加されたプロパティなのでVB.NET2003以前では利用できません。VB.NET2003以前で同じことをやろうとすると次のようになります。
Dim
Reader As
New IO.StreamReader("C:\Test.txt",
System.Text.Encoding.GetEncoding("Shift-JIS"))
Do
While Reader.Peek >= 0 Reader.Close() |
■リスト4
この場合は最後までファイルを読み込んだかの判定にPeekメソッドを使用しています。Peekメソッドがファイルに続きがない場合-1を返すことを利用します。なお、これ以上データがない状態でReadLineメソッドを呼び出すとNothingが返ってきますから、このNothingを捕まえて判定することもできます。値がNothingかどうか確認するにはIsNothing関数を使います。
ReadメソッドもReadLineメソッドと同様に使用することができますが、こちらは戻り値がInteger型であることに注意してください。つまり戻り 値をそのまま扱うと数値になるのです。この数値は文字コードを表しています。
文字コードから文字を得るにはChr関数(読み方:Chr = キャラ)を使いますから、Readメソッドを利用して一文字ずつ読み込んでいく例は次のようになります。
Dim
Reader As
New IO.StreamReader("C:\Test.txt",
System.Text.Encoding.GetEncoding("Shift-JIS")) Dim Code As Integer
Do
While Reader.Peek >= 0 Reader.Close() |
■リスト5
VB2005の場合は、この場合もEndOfStreamプロパティを利用することができますが例は挙げないでおきます。この例のようにReadメソッドを使って1文字ずつファイルを読み込んでいくというのはかなりこったプログラムを作る時だけでしょう。
さて、ここでファイルの読み込みのもう少し実際的な例として、CSV形式の郵便番号データを読み込んで住所を表示する例を紹介しましょう。
まず、郵政公社のサイトから郵便番号データをダウンロードして下さい。都道府県ごとにダウンロードできますので、ここでは一番データ量の少ない山梨県のデータをダウンロードすることにします。
http://www.post.japanpost.jp/zipcode/dl/kogaki/lzh/19yamana.lzh
ダウンロードしたファイルを回答すると19YAMANA.CSVという名前のファイルができますので、このcsvファイルをCドライブの直下においてください。つまり、C:\19YAMANA.CSVというファイルが存在するようにしてください。
このファイルをメモ帳で開いてみると次のような内容になっています。単にファイルをダブルクリックするとExcelで開いてしまいますが、Excelは適当にデータをセルごとに配置してしまいます。ファイルの内容を素直に見るのにはメモ帳などのテキストエディタが適しています。
19201,"400
","4000000","ヤマナシケン","コウフシ","イカニケイサイガナイバアイ","山梨県","甲府市","以下に掲載がない場合",0,0,0,0,0,0 19201,"400 ","4000858","ヤマナシケン","コウフシ","アイオイ","山梨県","甲府市","相生",0,0,1,0,0,0 19201,"400 ","4000867","ヤマナシケン","コウフシ","アオヌマ","山梨県","甲府市","青沼",0,0,1,0,0,0 19201,"400 ","4000828","ヤマナシケン","コウフシ","アオバチョウ","山梨県","甲府市","青葉町",0,0,0,0,0,0 (以下略) |
郵便番号や住所やその読み仮名が並んでいるのがわかると思います。正確には1番目から9番目の項目は次の順番が並んでいます。
位置 | 意味 | データの例 |
1 | 全国地方公共団体コード | 19201 |
2 | (旧)郵便番号(5桁) | 400 |
3 | 郵便番号(7桁) | 4000858 |
4 | 都道府県名 | ヤマナシケン |
5 | 市区町村名 | コウフシ |
6 | 町域名 | アイオイ |
7 | 都道府県名 | 山梨県 |
8 | 市区町村名 | 甲府市 |
9 | 町域名 | 相生 |
■表2
10番目以降の項目についてはここでは触れません。
このデータを元にして郵便番号と住所を表示させるプログラムは次のようになります。
Dim
Reader As New
IO.StreamReader("C:\19yamana.csv",
System.Text.Encoding.GetEncoding("Shift-JIS")) Dim Items() As String 'CSVの各項目を表す配列 Dim Line As String = Reader.ReadLine 'CSVの一行 Dim PostalCode As String '郵便番号 Dim Address As String '住所 Do Until IsNothing(Line) Items = Line.Split(",") '一行を, (カンマ)で区切って項目ごとに分解 PostalCode = Items(2)
'郵便番号取得 Address = Items(6) & Items(7) &
Items(8)
'都道府県名と市区町村名と町域名を結合 ListBox1.Items.Add(PostalCode & " - " & Address) Line = Reader.ReadLine '次の行を読み込む。 Loop Reader.Close() |
■リスト6
この例ではカンマで区切られた項目を一挙に分解するSplitメソッド(読み方:Split = スプリット)を使っている他は特に目新しいことはありません。実行結果は次のようになります。
■画像3:リスト6の実行結果
CSV形式では項目がカンマ区切りで並んでいるために、各項目の値を取得するには、まず一行分のデータをReadLineメソッドで取得した後にカンマごとに項目を分解する処理が必要です。幸いこの処理はSplitメソッドがやってくれるので楽です。Splitメソッドは分解した内容を配列として返しますので、あらかじめ用意しておいて配列Itemsでこれを受け取ります。
そうすると、前に掲載した表のように簡単に各項目の値を取得することができます。たとえば、7桁の郵便番号を取得したければItems(2)です。表では7桁の郵便番号が3番目になっているのでItems(3)としてしまいそうですが、配列が0から始まるという点に着目すると表の項目位置と配列のインデックスが1つずつずれていることにすぐに気が付くことでしょう。
Replaceメソッド(読み方:Replace = リプレイス)は " (ダブルクォーテーション)を削除するために使用しています。この意味がわからない場合はReplaceの行をコメントにして実行してみるとすぐ分かります。
もうちょっと役に立ちそうな例も紹介しましょう。テキストボックスとラベルを配置して、テキストボックスに入力した郵便番号に対応する住所がラベルに表示するようにしてみましょう。完成版のイメージは次の通りです。このプログラムは上記のプログラムをちょっと改造するだけで済むので時間のある方は是非自分でチャレンジして同じものを作ってみてください。
■画像4:リスト7の実行結果
プログラムは次のようになります。
Private
Sub Button1_Click(ByVal
sender As System.Object,
ByVal e As System.EventArgs)
Handles Button1.Click
Dim
Reader As New
IO.StreamReader("C:\19yamana.csv",
System.Text.Encoding.GetEncoding("Shift-JIS")) Do Until IsNothing(Line) Items = Line.Split(",") '一行を, (カンマ)で区切って項目ごとに分解
PostalCode = Items(2)
'郵便番号取得
If PostalCode = TextBox1.Text
Then Line = Reader.ReadLine '次の行を読み込む。 Loop Reader.Close() End Sub |
■リスト7
参考:郵便番号データのダウンロード
http://www.post.japanpost.jp/zipcode/download.html
次はファイルへ文字を書き込む方法を説明します。プログラムは読み込む例とかなり似たものとなります。
次の例ではテキストボックスに入力された内容をC:\VBTest.txtに書き込みます。C:\VBTest.txtが存在しない場合には新しくファイルが作成されます。
Dim
Writer As New
IO.StreamWriter("C:\VBTest.txt") Writer.WriteLine(TextBox1.Text) Writer.Close() |
■リスト8:ファイルへの書き込み
テキストに書き込むには通常StreamWriterクラス(読み方:StreamWriter = ストリームライター)を使用します。このような簡単なプログラムでファイルに書き込めるのですから楽なものです。
実際に試してみるとわかるのですが、このプログラムは書き込むたびにテキストファイルの以前の内容をすべて削除して、その都度新しく書き込みます。たとえば、先に Apple とテキストファイルに書き込んでおいて、後からBananaと書き込むと、Appleは削除されてしまいます。
これはStreamWriterクラスの標準の動作で、ファイルを閉じるまではどんどん文字を追加していけるのですが、いったん閉じた後で再度書き込もうとするとこのような現象になるのです。しかしながら、引数を指定することによってこの標準の動作を変更することもできます。
Appleを残して追加してBananaを書き込みたい場合にはStreamWriterクラスに引数を追加して、次のようにします。
Dim
Writer As New
IO.StreamWriter("C:\VBTest.txt",
True) Writer.WriteLine(TextBox1.Text) Writer.Close() |
■リスト9:ファイルへの追加書き込み
書き込むときに文字コードを指定するにはStremWriterクラスのコンストラクタのさらに第3引数を指定します。これはStreamReaderの場合と似ています。たとえば、UTF-8で書き込むには次のようにします。
Dim
Writer As New
IO.StreamWriter("C:\VBTest.txt",
True, System.Text.Encoding.UTF8) Writer.WriteLine(TextBox1.Text) Writer.Close() |
■リスト10:文字コードの指定
以上ではテキストファイルに1行ずつ書き込むWriteLineメソッド(読み方:WriteLine = ライトライン)を紹介しましたが、1文字ずつ書き込むWriteメソッド(読み方:Write = ライト)も用意されています。私はWriteLineメソッドのほうが扱いやすいので普段はこちらを使用しています。
テキストファイルの読み書きができるようになったとことで、これを活用したフォームの位置を記憶して復元するプログラムを作ってみます。このプログラムではフォームを閉じるときにフォームの座標と大きさをテキストファイルに書き込んでおいて保存しておきます。そして、次にフォームを開いたときに最後に閉じた位置に閉じたときの大きさで表示されるようにします。
Private
Sub Form1_Load(ByVal
sender As System.Object,
ByVal e As System.EventArgs)
Handles MyBase.Load
If
IO.File.Exists("C:\FormPosition.txt")
Then |
Private Sub
Form1_FormClosing(ByVal sender
As Object,
ByVal e As
System.Windows.Forms.FormClosingEventArgs) Handles
Me.FormClosing Dim Writer As New IO.StreamWriter("C:\FormPosition.txt") Writer.WriteLine(Me.Left) End Sub |
■リスト11:位置復元プログラム(VB2005)
VB.NET2002およびVB.NET2003ではFormClosingイベントがないので、Closingイベントを使用します。イベントが変るだけでプログラムは同じなのですが念のために掲載しておきます。
Private
Sub Form1_Load(ByVal
sender As System.Object,
ByVal e As System.EventArgs)
Handles MyBase.Load
If
IO.File.Exists("C:\FormPosition.txt")
Then End Sub |
Private Sub
Form1_Closing(ByVal sender
As Object,
ByVal e As
System.ComponentModel.CancelEventArgs) Handles
MyBase.Closing Dim Writer As New IO.StreamWriter("C:\FormPosition.txt") Writer.WriteLine(Me.Left) End Sub |
■リスト12
最後に特殊な形式のテキストファイルの読み書きについて簡単に言及しておきます。
csvファイルについては本文中で触れたように、行ごとに読み込んでSplitメソッドで切り分けるのが基本となります。ただし、項目の値自体にカンマが含まれている場合はこの手法は通用しません。1文字ずつ自分で読み込んで処理することになります。
固定長ファイルの場合も行ごとに読み込んで、Mid関数などを使って自分で項目を切り分けていくことになります。
iniファイルの場合はAPI関数のGetPrivateProfileString関数を使用すれば、テキストファイルとして処理するよりも楽に読み込むことができます。書き込み用にはSetPrivateProfileString関数も用意されています。ただし、これらの関数はVBや.NET Frameworkの関数ではないので扱い方がVBの常識とは異なります。APIについては中級講座で取り上げる予定です。なお、マイクロソフトはiniファイルの代りにレジストリを使用することを推奨しているようです。
xmlファイルの場合はxmlファイルを読み書きするためにXmlTextReaderクラス(読み方:XmlTextReader = エックスエムエルテキストリーダー)等の専用のクラスが用意されていますのでそちらを使うのが通常です。今回説明したStreamReaderを使用して読み込むことももちろんできますが、この場合xmlの独特の構造を解析プログラムを自分で書かなければなりません。