連載

プロフェッショナルVB.NETプログラミング

第8回 ファイル入出力(後編)

(株)ピーデー
川俣 晶
2002/06/08

Page1 Page2 Page3

テキスト・ファイルの書き込み位置

 VB 6では、Seek関数により、書き込み中のファイルの先頭からの位置を知ることができる。また、Seekステートメントでこの位置を変更することができる。以下はそれを示すサンプル・プログラムである。

  1: Private Sub Form_Load()
  2:   Dim fileno As Integer
  3:   fileno = FreeFile
  4:
  5:   Open "c:\test.txt" For Output As #fileno
  6:   Print #fileno, "亜意宇";
  7:   Dim p As Long
  8:   p = Seek(1)
  9:   Print #fileno, "えお"
 10:   Seek #1, p
 11:   Print #fileno, "得御"
 12:   Close #fileno
 13:
 14:   Dim s As String
 15:   Open "c:\test.txt" For Input As #fileno
 16:   While Not EOF(fileno)
 17:     Line Input #fileno, s
 18:     Debug.Print s
 19:   Wend
 20:   Close #fileno
 21:   Debug.Print p
 22: End Sub
Seek関数とSeekステートメントを使用したVB 6のサンプル・プログラム5

 これを実行すると以下のようになる。

 1: 亜意宇得御
 2:  7
サンプル・プログラム5の実行結果

 これと同等の機能を持つVB.NETのプログラムは以下のようになる。

  1: Public Class Form1
  2:   Inherits System.Windows.Forms.Form
  3:
  4: #Region " Windows フォーム デザイナで生成されたコード "
  5:
  6:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
  7:     Dim fileno As Integer
  8:     fileno = FreeFile()
  9:
 10:     FileOpen(fileno, "c:\test.txt", OpenMode.Output)
 11:     Print(fileno, "亜意宇")
 12:     Dim p As Long
 13:     p = Seek(1)
 14:     Print(fileno, "えお")
 15:     Seek(1, p)
 16:     PrintLine(fileno, "得御")
 17:     FileClose(fileno)
 18:
 19:     Dim s As String
 20:     FileOpen(fileno, "c:\test.txt", OpenMode.Input)
 21:     While Not EOF(fileno)
 22:       s = LineInput(fileno)
 23:       Trace.WriteLine(s)
 24:     End While
 25:     FileClose(fileno)
 26:     Trace.WriteLine(p)
 27:   End Sub
 28: End Class
サンプル・プログラム5をVB.NETで書き換えたサンプル・プログラム6

 これを実行すると以下のようになる。

 1: 亜意宇得御
 2: 7
サンプル・プログラム6の実行結果

 見てのとおり、ほとんど同じ機能を持つSeek関数が提供されているので、VB.NETへの移行は難しくない。だが、クラス・ライブラリを使用するように書き換える場合はどうだろうか? 以下は同等の機能をクラス・ライブラリのStreamWriterクラスを使用して記述してみた例である。

  1: Imports System.IO
  2:
  3: Public Class Form1
  4:   Inherits System.Windows.Forms.Form
  5:
  6: #Region " Windows フォーム デザイナで生成されたコード "
  7:
  8:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
  9:     Dim writer As StreamWriter, reader As StreamReader
 10:     writer = New StreamWriter("c:\test.txt")
 11:     writer.Write("亜意宇")
 12:     Dim p As Long
 13:     writer.Flush()
 14:     p = writer.BaseStream.Position
 15:     writer.Write("えお")
 16:     writer.Flush()
 17:     writer.BaseStream.Position = p
 18:     writer.WriteLine("得御")
 19:     writer.Close()
 20:
 21:     reader = New StreamReader("c:\test.txt")
 22:     Do
 23:       Dim s As String
 24:       s = reader.ReadLine()
 25:       If s = Nothing Then Exit Do
 26:       Trace.WriteLine(s)
 27:     Loop
 28:     reader.Close()
 29:     Trace.WriteLine(p)
 30:   End Sub
 31: End Class
サンプル・プログラム6を、クラス・ライブラリを使用して書き換えたVB.NETのサンプル・プログラム7

 これを実行すると以下のようになる。

 1: 亜意宇得御
 2: 9
サンプル・プログラム7の実行結果

 このプログラムにはいくつか、初めてお目に掛かるキーワードがある。まずFlushメソッドはまだ書き込まれていないデータを書き込む働きがある。パフォーマンスを向上させるために、書き込みデータのバッファリングが行われているため、確実に書き込むためには、このメソッドを呼び出す必要がある。ただし、ファイルを閉じるときには確実に書き込まれるため、通常は間違いなく閉じるようにすれば問題ない。問題が起きるのは、閉じる前に書き込み位置を変更したりする場合である。

 次に、BaseStreamというプロパティは、実際に入出力を行うオブジェクトを参照する機能を持つ。そして、Positionプロパティは、書き込み位置を設定/取得できるプロパティである。つまり、PositionプロパティはVBのSeek関数、Seekステートメントとほぼ同じ役割を持つものである。バッファリングはStreamWriterクラスで、位置の管理はBaseStreamプロパティで参照されるクラスで行われるため、Positionプロパティを読み書きする場合は、その前にFlushメソッドを呼んでバッファを書き込んでおく必要がある。

 さて、出力されている最後の数値を見ていただきたい。“7”だったはずの数値が、クラス・ライブラリを使うと“9”に化けている。値が違うことには2つの理由がある。1つは、1文字の占めるバイト数がシフトJISとUTF-8では違うこと。漢字や「かな」は3bytesを要するため、シフトJISよりも多くの容量を占有する。もう1つの理由は、位置を数える起点となる位置が違うためだ。VBではファイルの先頭のバイトは1とするが、クラス・ライブラリでは0としている。この違いが、値が1つずれる原因となっている。

文字列サイズの変化

 シフトJISに慣れたプログラマー向けに1つ余談を書こう。以下のようなソースがあるとする。

  1: Imports System.IO
  2:
  3: Public Class Form1
  4:   Inherits System.Windows.Forms.Form
  5:
  6: #Region " Windows フォーム デザイナで生成されたコード "
  7:
  8:   Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
  9:     Dim writer As StreamWriter, reader As StreamReader
 10:     writer = New StreamWriter("c:\test.txt")
 11:     writer.Write("亜意宇")
 12:     Dim p As Long
 13:     writer.Flush()
 14:     p = writer.BaseStream.Position
 15:     writer.Write("得御可")
 16:     writer.Flush()
 17:     writer.BaseStream.Position = p
 18:     writer.Write("ΘΛ¶")
 19:     writer.Close()
 20:
 21:     reader = New StreamReader("c:\test.txt")
 22:     Do
 23:       Dim s As String
 24:       s = reader.ReadLine()
 25:       If s = Nothing Then Exit Do
 26:       Trace.WriteLine(s)
 27:     Loop
 28:     reader.Close()
 29:     Trace.WriteLine(p)
 30:   End Sub
 31: End Class
シフトJISとUTF-8の違いを示すVB.NETのサンプル・プログラム8

 これを実行すると以下のようになる。

 1: 亜意宇ΘΛ¶可
 2: 9
サンプル・プログラム8の実行結果

 このプログラムでは、文字列「得御可」を「ΘΛ」で上書きしている。にもかかわらず、「得御」は消えたのに「可」は残っていて、出力されてしまっている。その理由は何だろうか?

 これは、UTF-8で表現したとき、「得御可」はいずれも1文字3bytesで表現されるのに対して、「ΘΛ」は1文字2bytesで表現されているためだ。つまり、シフトJISで2bytesだった文字がすべて3bytesになるという前提を置くことはできない。従ってこのようにして、すでに書き込んだ文字を上書きする場合は、同じ文字数を書いても上書きになるとは限らない。十分に注意していただきたい。

次回予告

 次回はオブジェクト関連の機能を取り上げたいと考えている。End of Article


 INDEX
  連載 プロフェッショナルVB.NETプログラミング
  第8回 ファイル入出力(後編)
    1..NET Frameworkクラス・ライブラリを用いたテキスト・ファイルの入出力
    2.エンコーディング名を指定したテキスト・ファイルの入出力
  3.テキスト・ファイルの書き込み位置
 
「プロフェッショナルVB.NETプログラミング」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間