2024/08/29

テキストファイルの読み書き(ADODB.Stream編)




VBAを使い「テキストファイルを読み書き」する手法について、以下のようなシリーズで紹介しています。
 ①テキストの文字コードについて
 ②Openステートメント法による読み書き
 ③ADODB.Stream法による読み書き   ←今回
 ④TextStream法による読み書き
 ⑤XMLHTTP法によるWebデータの読み取り   (作成中)
 ⑥Web上からダウンロードしての読み取り   (作成中)
今回紹介する「ADODB.Stream法」は、下表のように様々な文字コードに対応できる手法です。
なお操作可能なのは、LAN上(ドライブ名やサーバー名で始まるパス名の場所)のファイルのみです。
読み書き手法対応文字コードファイルの場所
Shift-JISUTF-8UTF-16LAN上Web上
Openステートメント
ADODB.Stream
TextStream(FileSystemObject)
XMLHTTP+ADODB.Stream
図01

手法全体の話ですが、図01のように手法ごとに「対応できる文字コードが異なる」原因の1つとして、手法の生まれた時期と関係がありそうです。下図は各手法の歴史です。今回紹介するADODBは1996年から存在したことが分かります。
テキストファイルの操作手法の歴史
図02

同様に、各文字コードの歴史をまとめたのが下図です。ADODBが生まれた頃には、Shift-JISはもちろんUnicodeも既に存在しているので、主な文字コードには対応させたようです。
文字コードの歴史
図03

以下では、ADO および ADODB.Streamオブジェクトについて説明します。

1.ADOライブラリ 

ADOは「ActiveX Data Objects」の略で、Microsoftが提供する「外部データベースに接続」するためのツールです。
この接続可能なデータベースには、データベースアプリである「SQL Server」や「Access」、またワークシートをテーブル相当として使えるExcelなどがあります。
これに加えて、テキストの並びをテーブル相当として使える「テキストファイル(CSVファイル)」も対象となりますし、普通のテキスト読み書き処理もその機能の一部として使えるようです。
まずADOライブラリの概要とそのオブジェクト生成法について整理します。なお「Stream」はオブジェクト名で、その頭についている「ADODB」は、オブジェクトを提供するアプリケーションの名前になります。

1ー1.ADOオブジェクトの種類

ADO直下には多くのオブジェクトが存在します。図04はそのオブジェクトが、ADOのバージョンにより使えるか否かと、実行時バインディング時にVBAのCreateObjectで生成可能か否かをまとめたものです。
オブジェクト
(カッコはコレクション)
内容事前バインディング実行時バインディング
Ver 2.8Ver 6.1CreateObject可
Commandデータソースに対して実行する特定コマンドの定義
Connectionデータソースへの接続
Error発生するアクセスエラーの詳細情報
(Errors)×
Fieldデータ列××
(Fields)××
ParameterCommandオブジェクトに関連付けられた引数
(Parameters)×
PropertyADOの動的特性×
(Properties)×
RecordRecordsetの行、又はファイルシステム内のディレクトリやファイル
Recordsetコマンド実行により返された結果
Streamデータの流れ
図04

今回のStreamオブジェクト(一番下)には使えないバージョンはありませんし、またCreateObjectでも生成可能です。

1ー2.ADOオブジェクト生成

ADOのオブジェクトを使うには、まずオブジェクトを生成する必要があります。その生成方法には以下の2種類があります。
 ・「事前バインディング」・・・コードを実行する前に生成
 ・「実行時バインディング」・・コードを実行した時に初めて生成
事前バインディングの特徴は、コード作成時にインテリセンス(コード補完機能=使用可能なプロパティ等が表示され、選択することが可)が使える事と併せ、効率が良い(≒実行速度が速い)ことです。
一方実行時バインディングは、実行しているPCに合わせたライブラリを呼び出すため、PC環境の異なる複数ユーザーにマクロを配布する時でも安全度は高いと思われます。

1ー2ー1.事前バインディング

事前バインディングでは、VBE(コードを書くウィンドウ)上部の「ツール」→「参照設定」から、図05のように「Microsoft ActiveX Data Objects X.X Library」にチェックをし有効にします。
ADOの参照設定
図05

X.Xの部分はバージョンを表していますが、最新Excelでは「2.8」①までと、少し飛んで「6.1」②が選択できるかと思います。
通常は「最新のもの」を選択するのが良いとされています。しかし複数のPCを対象に動かすようなシステムの場合に「最新バージョン」を選択して作ってしまうと、もしその「最新バージョン」がインストールされていないPCが存在する場合は、うまく動かない可能性が出てきます。ライブラリを選択する際には「どのPCにも入っているバージョン」を選ぶのが良いと思います。
また図04のFieldオブジェクトのように「使用できないバージョン」もあるので、事前確認が必要です。
プログラム的には、図05のように「ADOのライブラリーを参照設定」した上で、図06のように「オブジェクト変数をADOオブジェクトとして宣言」→「New句を使って生成」した後、オブジェクト変数を処理に使用します。
  1. '========== ⇩(1) 事前バインディングでの宣言と生成 ============
  2.  Dim ADOst As ADODB.Stream   '←Streamオブジェクト型変数の宣言
  3.  Set ADOst = New ADODB.Stream   '←Streamオブジェクトの生成
  4.  
  5.  (生成したStreamオブジェクト変数を使い、テキストやバイナリの処理)
図06

01行目「Dim ADOst As ADODB.Stream」では、Streamオブジェクト型で変数宣言をします。
03行目「Set ADOst = New ADODB.Stream」では、New句を使ってオブジェクトを生成します。
オブジェクト生成後は、そのオブジェクト変数(図06では、ADOst )を使って処理作業を行います。

1ー2ー2.実行時バインディング

実行時バインディングの場合は、使用するオブジェクトをCreateObjectを使って生成します。
  1. '========== ⇩(2) 実行時バインディングでの宣言と生成 ============
  2.  Dim ADOst As Object   '←一般オブジェクト型変数の宣言
  3.  Set ADOst = CreateObject("ADODB.Stream")   '←Streamオブジェクトの生成
  4.  
  5.  (生成したStreamオブジェクト変数を使い、テキストやバイナリの処理)
図07

11行目「Dim ADOst As Object」では、単なるObject型として変数宣言をします。
13行目「Set ADOst = CreateObject("ADODB.Stream")」では、CreateObject関数を使ってオブジェクトを生成します。オブジェクト生成後は、そのオブジェクト変数(図07では、ADOst )を使って処理作業を行います。
なお今回のサンプルコードは、全て実行時バインディングでオブジェクト生成をしています。

2.ADODB.Streamのプロパティ

Streamオブジェクトのプロパティには、以下があります。
プロパティ内容既定値
Typeデータの種類adTypeText(値=2)
CharSet文字セット"Unicode"
LineSeparator行区切り文字adCRLF(値=-1)
Position現在の位置0(データ先頭位置)
Sizeストリームのバイトサイズ0
EOS現在位置が末尾か否か(True/False)-
Mode可能なデータ変更権限adModeUnknown(値=0)
StateObjectの開閉状態adStateClosed(値=0)
図08

2-1.Type

このTypeプロパティをどの値にするかで、ADODB.Streamオブジェクトを「テキストデータ」の読み書きツールとして使うのか、「バイナリデータ」のツールとして使うのかが決まります。下記の2種があります。
定数内容
adTypeBinary1バイナリデータ
adTypeText2テキストデータ(既定)
図09

なお表内の定数は「ADOライブラリを参照設定」した時にのみ通用します。参照設定をしない場合は「値」列の数値を使用するか、事前に定数宣言等をして下さい(以降のプロパティ/メソッドも同じです)。
このプロパティ値は、設定/取得が可能ですが、「設定」は以下の条件の時のみです。
 ・StreamをOpenしていない状態
 ・Openしていて、Positionが先頭(Position=0)にある時(Streamが空である必要は無い)
またTypeプロパティ値により、Streamオブジェクト内で使えるプロパティ・メソッドが以下の様に制限されます。
(下記以外は、どちらでも使えます)
プロパティ/メソッドType=
1(バイナリ)2(テキスト)
プロパティ CharSet×
LineSeparator×
メソッド Read×
ReadText×
Write×
WriteText×
SkipLine×
図10

Type=2(既定値)に設定すると、Stream内でデータ(バイト値)をテキストとして扱うために「文字コード(CharSetプロパティ)」や「改行マーク(LineSeparatorプロパティ)」が必要となりますが、バイナリの場合(Type=1)には不要です。ですので、Type=1ではCharSet・LineSeparatorを設定しようとすると「このコンテキストでは操作は許可されていない」とのエラーが発生します。
メソッドについても同様で、データを読み書きするためのRead/Writeメソッドはバイナリ用、ReadText/WriteTextメソッドはテキスト用と使い分けるようになっています。また、改行までの1行分をスキップするSkipLineメソッドも、改行の概念があるテキストのみのメソッドです。
寄り道(テキスト/バイナリが自動認識)
Microsoftのサイトでは「新しい空のStreamにバイナリデータを書き込むと、Typeプロパティ値はadTypeBinary(値=1)になる」と記載されていますが、色々試してみても再現できません("バイナリデータを書き込む" という意味が理解できません)。
テキスト/バイナリが自動認識されるとすると、それはスゴイ事ですが、勝手に変わられても困まる気がします。

2-2.CharSet

CharSetプロパティは、読み書きするテキストファイルの文字コードです。既定値は「Unicode」で、これは「UTF-16LE(BOM付)」のことです。
値の取得/設定が可能ですが、「設定」は以下の条件の時のみです。
 ・StreamをOpenしていない状態
 ・Openしていて、Positionが先頭(Position=0)にある時(Streamが空である必要は無い)
このPosition=0の時に「異なる文字コードに変更」したとしても、既にStream内に存在していた文字(=バイト列)が変わってくれる訳ではありません。文字コードを変更した時点から「Streamに格納する文字を、設定した文字コードで管理する」という意味です。
つまり、元の文字コードのデータを残したまま「文字コードを変更」した場合、新たに格納する文字量が多ければ「元の文字を全て上書き」してくれますが、元の文字コードでの文字量が多かった場合には「新たな文字の後ろ側に元の文字コードのバイトが残る」ことになります。現象としては「後ろ側が文字化け」します。
これを防ぐために、文字コードを途中で変更する場合は「SetEOSメソッドでStream内をクリア」することが重要と思います。
CharSetに指定できる値(ワード)は、基本的には下図のように「レジストリのリスト」に載っている「文字コード」ワードのようです。(但し、指定するとエラーになるものも存在します)。
レジストリに登録されている文字コード
図11

なお、このレジストリの中に「"UTF-16"」は存在しませんが、試してみるとCharSetプロパティに設定可能です。もちろん、上記で説明したCharSet既定の「Unicode」と同じ役目を果たしてくれます。
そこでレジストリに載っているものに加え「"UTF-16"」のようなもっともらしい名前をCharSetに指定し、どの文字コードに対応するかを試してみた結果が以下になります。なお文字コードはWindows付属のメモ帳で選択できる範囲+αに絞りました。
Charset=UTF-8UTF-16LEUTF-16BEShift-JIS
BOM無BOM付BOM無BOM付BOM無BOM付





_autodetect
csShiftJIS
csWindows31J
ms_Kanji
shift_jis
shift-jis
unicode
unicode-1-1-utf-8
unicode-2-0-utf-8
unicodeFFFE
utf-8
x-ms-cp932
x-sjis
x-unicode-2-0-utf-8



utf-16
utf-16le
utf-16be
sjis
◎:Stream内の文字コード 〇:読み取り可 △:一部文字化け
図12

試してみて「文字コードに対応する」という意味には2通りあることがわかりました。
1つ目は「Stream内での文字データの管理の方法」で、図12の範囲では「◎印」で示している4種類です(図13)。
文字コードBOM有無CharSetの指定例
UTF-8utf-8
UTF-16LEunicode
UTF-16BEutf-16be
Shift-JIS-shift-jis
図13

各文字コードの特徴は「文字コード編」を参照して頂くとして、例えば "Unicode" をStreamのCharSetに指定すると、Stream内ではテキストをUTF-16LE(BOM付き)で管理する事になります。つまり「半角でも全角でも2バイト/文字」+「先頭2バイトにはBOM(0xFFFE)が入る」という状態です。
一方 "UTF-8" を指定すると、「半角アルファベット+数字は1バイト、半角カナや全角文字は3バイト」+「先頭3バイトにはBOMが入る」状態になります。
もう1つは「テキストファイルを正しく読み込めるか否か」で、図12では「〇印」や「△印」で示しています。テキストファイルを読み込むには「LoadFromFile」メソッドを使用します。
もちろん、テキストファイルの「UTF-8」「UTF-16」「Shift-JIS」などの基本的な文字コードは合っていないとダメです。その上で、テキストファイル側の文字コード環境(バイト順やBOM有無)が「Stream内の文字コード環境に受け入れられるのか」という問題のようです。
図13の4項目をもう少し詳しく説明します。なお①~④の番号は、図13の番号と合わせています。
①StreamがUTF-8(BOM付き)の場合は、テキストファイルがBOM無しでも正しく取り込んでくれるようです。なおStreamに取り込んだ後は、BOM付きとして管理されます。
②StreamがUTF-16LE(BOM付き)の場合、バイト順がLE(Little-Endian)であればBOM無しでも正しく取り込んでくれます。この場合もStream内ではBOM付きとして管理されます。
一方バイト順がBE(Big-Endian)のテキストファイルは、BOMが付いていれば正しく取り込みます。これはBOM情報を見て、バイト順を修正しながら取り込んでいるのだと考えられます。ですのでBOMが付いていないUTF-16BEは、文字化けします。
③StreamがUTF-16BE(BOM無し)の場合、BOM付きのUTF-16BEのテキストファイルを読み込むと「先頭のBOMを何かの文字と判断」してしまうようで、先頭のみ文字化けとなります(図12の△印)。LE(Little-Endian)の場合は全くダメでした。
④Shift-JISではBOMも無く、UTFのような難しさは無さそうです。
なお図12でも分かるように、同じ文字コードでも複数の「文字コード」ワードが選択できる事が分かります。しかし、PCの環境により使えない場合も考えられますので確認は必要と思います。
また、なぜ「レジストリに載っていない文字コードワードが有効なのか?」ですが、おそらくADODB.Streamの実行ファイルに書き込まれていると考えるしかないのですが、詳細は調べ切れず、図12の下側のような「思いつき」のレベルに留まったのは残念です。
そのため図13以外の「UTF-8(BOM無し)」「UTF-16LE(BOM無し)」「UTF-16BE(BOM有り)」で管理するためのCharSetワードは、残念ながら見つけられていませんし、「UTF-32」もエラーとなるので、UTF-32ファイルを開けるのかも不明のままです。
Stream内にデータを取り込むには、上記のLoadFromFile以外に「WiteText」メソッドを使用する方法がありますが、WriteTextでは素直に「Stream内の文字コード環境に合わせて格納」される事になります。

2-3.LineSeparator

「LineSeparator」プロパティは、改行マークの設定です。以下の3種が選べます。
定数内容
adCRLF-1vbCrLf相当(既定)
adLF10vbLf相当
adCR13vbCr相当
図14

このLineSeparator値には2つの役割があります。
1つ目が「ReadTextメソッドで1行ずつ読み込む(引数にadReadLine(値=-2)を指定)時の、行を分ける改行マーク」の判別です。
改行コードには「CrLf(0x0D 0A)」「Lf(0x0A)」「Cr(0x0D)」と3種あります。Excelでは主にCrLfとLfが使われますが、下図のようにメモ帳上ではCrでも改行と見なしてくれます。
(作り方:バイナリエディタで改行コードを操作した後、再度メモ帳で開いています。)
メモ帳とバイナリエディタで確認する改行マークの種類
図15

そのため下表のように、テキストの中で使われる改行コードに合わせて「LineSeparator」の値を設定する必要があります。
改行コードCrLfLfCr
adCRLF(既定)××
adLF(〇)×
adCR(〇)×
図16

テキストの改行が「CrLf」の場合は、上表の通りLineSeparator値に何を指定しても行単位で切り出してくれます。しかしカッコ付きとしているのは「カス」が残るからです。例えばCrLfに対し「adLF」を指定すると文字列の最後に「Cr」が残ってしまいますし、「adCR」を指定すると文字列の先頭に「Lf」が残る事になるので注意が必要です。
この場合、もし残ったCrやLfを削除するのであれば、Replace関数で「""(長さゼロの文字列)」に変換をします(Trim関数では削除できません)。
2つ目が「WriteTextメソッドの第2引数に『adWriteLine(値=1)』を指定した時の、テキストに付加される改行マーク」です。例えば以下のコードのように、LineSeparator値を変えながら改行を指定してみます。
  1. '========== ⇩(3) WriteTextでの改行コード ============
  2. Private Sub test01()
  3.  Dim ADOst As Object
  4.  Set ADOst = CreateObject("ADODB.Stream")
  5.  With ADOst
  6.   .type = 2
  7.   .Charset = "Shift-JIS"
  8.   .Open
  9.    .LineSeparator = -1    '←CrLf
  10.    .WriteText "abc", 1    '←①
  11.    .LineSeparator = 10    '←Lf
  12.    .WriteText "def", 1    '←②
  13.    .LineSeparator = 13    '←Cr
  14.    .WriteText "ghi", 1    '←③
  15.    .savetofile "Test_SJIS.txt", 2
  16.   .Close
  17.  End With
  18.  Set ADOst = Nothing
  19. End Sub
図17

30行目「.LineSeparator = -1」では、LineSeparatorプロパティにadCRLF(値=-1 :図14参照)を指定していますので、改行コードは「CrLf」となります。その改行コードで31行目「.WriteText "abc", 1」を実行し、文字列"abc"の最後に改行コードを追加しています。
33行目「.LineSeparator = 10」は改行コードとしてLfを、36行目「.LineSeparator = 13」はCrを追加しています。
このコードでの結果は以下のようになります。
WriteTextで付ける改行マークの種類
図18

メモ帳上(図18の左側)ではどれも同じ「改行」に見えますが、バイナリエディタ(図18の右側)で見ると、改行のコードがそれぞれ異なる事が分かります。このように、WriteTextメソッドで付加する改行コードにもLineSeparatorプロパティ値が効いてきます。
ちなみにメモ帳の改行コードは「Windows(CRLF)」と表示されていますが、これは先頭の改行コードから判断しているようです。メモ帳の改行コード表示を鵜呑みにしない事も大切です。
なお、LoadFromFileでテキストファイルをStreamに読み込む際、「テキストファイル側の改行マークをStream側のLineSeparator値に合わせる」事はしないようです。例えば、既定のLineSeparator(=CrLf)のStreamに、Lf を改行マークとしたファイルを読み込んでも、Stream内では Lf のままとなります。ですので事前にファイル側の改行マークを確認し、その改行マークにStream設定を合わせる等の対応が必要となります。

2-4.Position

「Position」プロパティはStream内での現在位置を示し、OpenメソッドでStreamを開いている間のみ設定・取得が可能です。
Position値はStreamの先頭をゼロとした「バイト単位」の値で、設定の場合は「Stream内にあるデータのバイト数以下(0 ~ Stream.Size値)」にする必要があります。
テキストの読み書き(ReadText/WriteText)は、現在のPosition位置以降が処理の対象となります。しかし、例えば「ある文字と文字の間にPositionを移動しよう」とすると、結構大変な作業になります。
その理由として、テキスト型でStreamを扱う場合、設定した文字コード(CharSet値)で文字データは管理されるからです。つまり文字コードによって文字当たりのバイト数が変わってきますし、またBOMが付いたり付かなかったりもするので、目的のPosition位置を算出するには手間が掛かります。
下記は様々な文字コード環境で、「"abあい12"(半角2文字+全角2文字+数字2個)」という文字をStreamに格納した状態を表しています。
Streamの文字コード違いでPositionは異なる
図19

例えば「bの後ろ」にPositionを移動したい場合、設定した文字コードによって、全て異なる値の指定が必要と分かります。
もしそのようなPosition指定が必要な場合は、一旦VBA内に文字列として読み込み、処理をしてから再度Streamに戻す方が良さそうです。
また、バイナリの読み書き(Read/Write)やCopyToでのデータコピーにもPosition値が効いてきます。Positionを先頭や末尾(Stream.Size)以外の場所に移動させての操作には、慎重な位置計算が必要です。

2-5.Size

Sizeプロパティは、Stream内のデータのサイズを「バイト数」で戻します。値の取得のみ可能です。
CharSetの所でも説明しましたが、同じ文字列を読み込んでも、Stream内で管理する文字コードによりSize値が異なります。
下記のように、書き込み位置を文末に移動する場合などに使われます。
  1. '========== ⇩(4) Positionを移動して、既存ファイルに追記 ============
  2. Private Sub test02()
  3.  Dim ADOst As Object
  4.  Const FN As String = "Test_SJIS.txt"   '←読み書きするテキストファイル
  5.  Set ADOst = CreateObject("ADODB.Stream")
  6.  With ADOst
  7.   .type = 2   '←テキストとして処理
  8.   .Charset = "Shift-JIS"   '←テキストファイルの文字コードを指定
  9.   .Open
  10.    .LoadFromFile FN   '←テキストファイルからデータを読み込み(Positionはゼロのまま)
  11.    .Position = .Size   '←Positionを文末に移動
  12.    .WriteText "abc"   '←Positionの位置から文字をStreamに書き込み(=追記)
  13.    .SaveToFile FN, 2   '←ファイルに書き出し
  14.   .Close
  15.  End With
  16.  Set ADOst = Nothing
  17. End Sub
図20

71行目「.LoadFromFile FN」では、テキストファイルをStream内に読み込んでいます。
Stream内に読み込んだ段階では「Positionは先頭」に居ますので、このまま文字を書き込むと「先頭側から上書き」をしてしまうことになります。ですので72行目「.Position = .Size」で、Position位置を「文末(=Sizeプロパティ値)」に移動します。
Positionを文末に設定したら、73行目「.WriteText "abc"」で文字列を追加し、74行目「.SaveToFile FN, 2 」でテキストファイルにデータを戻しています。
この流れを図にしたのが以下になります。ここでは、テキストファイルに「"abあい12"」という文字が書かれており、その末尾に「"abc"」を追加しています。
SizeプロパティでPositionを文末に移動させ、文字列を追加
図21

なお、WriteTextで文字列を加えた時には「Position値は加えた文字列の末尾に移動」しますので、更にWriteTextで文字を加える時には、あえてPositionを移動させる必要はありません。

2-6.EOS

EOSプロパティは「End Of Steam」の略で、現在位置(=Position値)がStreamの末尾にあるか否かを True/False で示します。値の取得のみ可能です。
Openした直後は「Streamには何も無い」ので、「現在位置=文末」ということになり、EOS値はTrueとなっています。それ以降は、読み書きのステップごとに変わりますので、以下にまとめました。
なお下図の「Streamの状態」は、Stream内に文字列が格納されている状態を表し、且つ緑色の縦線は現在位置(Position値)を示しているつもりです。また読み書きする文字列は、"abc" の3文字としています。
Stream操作によるEOS値の変化
図22

Openした後、読み取り時(図22の上段)は、テキストファイルの内容をLoadFromFileメソッドで読み取ります。読み取るとファイルの文字はStreamに格納されますが、現在位置(Position値)は移動せず「文末では無くなる」ために、EOS値はFalseとなります。
Streamに入っている文字を読み取るにはReadTextメソッドを使います。ReadTextは「Positionの位置から後方」を読み取ります。読み取る文字数は引数指定でき「読み取った文字の末尾」へPositionは移動します。ですので全文字を読み取った場合は、Position=文末となり「EOS値はTrue」となります。
一方書き込み時(図22の下段)は、WriteTextメソッドを使って文字列をStreamに書き込みます。WriteTextは「Positionの位置から」文字を書き込み、「書き込んだ文字列の末尾」にPosition位置は移動します。ですので空のStreamに書き込んだ場合は、EOS値はTrueとなります。
Streamに書き込んだ文字をファイルに出力するにはSaveToFileメソッドを使用します。出力してもStream内の文字は消えませんが、現在位置はStream先頭に移動しますので、EOS値はFalseとなります。

2-7.Mode

Modeプロパティは、データ変更権限の設定です。取得/設定が可能ですが、設定はStreamを開く前(Openメソッドの実行前)にしかできないようです。
値の内容は以下のリストのようになっており、値としては0~27が設定可能(組み合わせが可能?)なようです。しかし、その組み合わせ方は良く分かりません。
定数内容
adModeUnknown0アクセス許可未設定(Streamの既定値)
adModeRead1読み取り専用のアクセス許可
adModeWrite2書き込み専用のアクセス許可
adModeReadWrite3読み取りと書き込みのアクセス許可
adModeShareDenyRead4他ユーザーが読み取りアクセス許可で接続を開くことを禁止
adModeShareDenyWrite8他ユーザーが書き込みアクセス許可で接続を開くことを禁止
adModeShareExclusive12他ユーザーが接続を開くことを禁止
adModeShareDenyNone16他ユーザーが任意のアクセス許可で接続を開くことを許可
図23

上表の内容を見ると「扱うファイルへの操作制限を掛ける事が可能」に思えますが、試してみると違いました。自プロセスでMode値を設定後、自プロセス・他プロセスから、どんな操作が可能かを調べた結果が下記です。(〇=操作可、×=操作不可)
Mode値123481216




Open
LoadFromFile×
ReadText×
WriteText×
SaveToFile×




Open-/〇〇/-〇/〇〇/〇〇/〇〇/〇〇/〇
LoadFromFile-/〇〇/-〇/〇〇/〇〇/〇〇/〇〇/〇
ReadText-/〇〇/-〇/〇〇/〇〇/〇〇/〇〇/〇
WriteText-/〇〇/-〇/〇〇/〇〇/〇〇/〇〇/〇
SaveToFile-/〇〇/-〇/〇〇/〇〇/〇〇/〇〇/〇
 他プロセスの記号は「自プロセス側がファイルをLoad時/自プロセス側がファイルをSave時」
図24

Mode=1では、ファイルがLoad出来ず、またStreamに書き込むことが出来ません。しかしファイルへ書き込みは可。
Mode=2では、ファイルはLoad出来るが読み込めず、Streamへの書き込みは可なのにファイルへの書き込みは不可。
それ以外のMode値では、特に支障なく操作可能なようです。
また他プロセスでは、ファイルへの読み書きが制限されるような事はありませんでした。なお「-/〇」等としているのは、自プロセス側でファイルをLoadやSaveが出来ないことを表しています。
この結果から考えると、Modeプロパティは「ConnectionオブジェクトやRecordオブジェクトで使われる場合にアクセス制限を掛ける」もののようです。そのため、今回のようにテキストファイルの読み書きとしてStreamオブジェクトを使う場合には、既定の「adModeUnknown(値=0)」のままとしておいた方が良さそうです。

2-8.State

Stateプロパティは、オブジェクトの状態を示します。取得のみ可で、以下の内容です。
なおMicrosoftでは「値の組み合わせとなる場合もあり」と説明しています。
定数内容
adStateClosed0閉じている(既定)
adStateOpen1開いている
adStateConnecting2接続している
adStateExecuting4コマンドを実行している
adStateFetching8行が取得されている
図25

なおテキスト操作で試した結果では、以下のようにStateが「 0 or 1 」以外となる条件は見つけられませんでした。
 ・StreamをCloseしている(Openしていない)・・・State = 0
 ・StreamをOpenしている・・・・・・・・・・・・State = 1
恐らくこのStateは「データベース接続・操作」で使う事を前提としているようなので、図25も「テキスト操作に不要そうな値は、見え消し」としておきました。

3.ADODB.Streamのメソッド 

Streamオブジェクトのメソッドは以下の内容になります。
メソッド内容
OpenStreamオブジェクトを開く
LoadFromFileファイルからStreamに読み込む
SaveToFileStreamの内容をファイルに保存
ReadTextテキストデータをStreamから読み込む
WriteTextテキストデータをStreamに書き込む
ReadバイナリデータをStreamから読み込む
WriteバイナリデータをStreamに書き込む
SkipLine1行全体をスキップ
SetEOSStreamの終わりの位置を設定
CopyToStreamの内容を別のStreamにコピー
Close Streamオブジェクトを閉じ、リソースを解放
Flushバッファの残データを全て吐き出す(≒ファイル書き込みを完了させる)
Cancelファイルの呼び出しを取り消す
図26

なおMicrosoftのサイトを見ると、Streamオブジェクトには「Statメソッド」が存在するようにも見えます。Statメソッドは「Streamの情報を取得」するもので、その情報とは「Streamの名前・サイズ・作成や変更、アクセスの時刻」等との事です。
しかし、Statメソッドは実際のライブラリの中には存在しません。
その代わりとして、サイズは「Sizeプロパティ」で取得できます。一方で、Streamの時刻関係の取得は不可能です。

3-1.Open

Openメソッドは、Streamオブジェクトを開きます。構文は以下です。
 Stream.Open [,Source][,Mode][,OpenOptions][,UserName][,Password]
パラメータとしては、下記5個が設定可能とのMicrosoftの説明です。
パラメータ省略可否内容既定値
1Source省略可Streamのデータソース(省略=Streamを開く)
2Mode省略可StreamのアクセスモードadModeUnknown(値=0)
3OpenOptions省略可開く際のOptionadOpenStreamUnspecified(値=-1)
(=既定Option?で開く)
4UserName省略可StreamオブジェクトのユーザーID無し
5Password省略可Streamオブジェクトのパスワード無し
図27

5個のパラメータの中には選択肢が複数存在するものもあります。しかし試してみると、Streamオブジェクトでは、全パラメータを規定値に設定した「Stream.Open , 0, -1, "", "" (途中のパラメータを省略してもOK)」のみが実行可能です。これ以外は「引数が間違った型、許容範囲外、または競合している」との実行時エラーとなります。
従ってテキストやバイナリを扱うStreamオブジェクトでは、パラメータ無し(=既定値)でOpenメソッドを実行するのが一般的で、他のサイトでも「Stream.Open」のみでStreamを開いています。

3-2.LoadFromFile

LoadFromFileメソッドは、ローカルファイルの内容をStreamに読み込みます。構文は以下です。
 Stream.LoadFromFile Filename
パラメータFilenameには、ファイルのパス+ファイル名を指定します。パス名無しでファイル名を指定した場合には、既定のドキュメントフォルダ「C:¥Users¥[User名]¥Documents¥」の場所にあるファイルと見なされるようです。
なお、そこに指定したファイルが存在しない場合は、エラーとなります。
LoadFromFileの特徴は2つです。
 ・Streamをクリアした後、ファイル内容をStreamに読み込む
 ・読み込み後のPositionは先頭(Position = 0)
図で表すと以下のようになります。
LoadFromFile実行後、元のStreamは全上書きされる
図28

Streamが空の状態(図28の左側)でも、また何か入っている状態(図28の右側)でも、LoadFromFile後は「読み込んだファイル内容のみ」となり、且つPositionの位置は先頭(ゼロ)となります。
LoadFromFileメソッドを使用したコード例は以下になります。
  1. '========== ⇩(5) ファイルからStreamに読み込む ============
  2. Private Sub test03()
  3.  Dim ADOst As Object
  4.  Const FN As String = "Test_SJIS.txt"
  5.  Set ADOst = CreateObject("ADODB.Stream")
  6.  With ADOst
  7.   .type = 2
  8.   .Charset = "Shift-JIS"
  9.   .Open
  10.    .LoadFromFile FN
  11.    MsgBox .ReadText
  12.   .Close
  13.  End With
  14.  Set ADOst = Nothing
  15. End Sub
図29

98行目「.Charset = "Shift-JIS"」では、Stream内での文字コード管理をShift-JISとしています。もちろん読み込むテキストファイル側もShift-JISとしないと、Stream内で文字化けが発生することになります。
101行目「.LoadFromFile FN」では、テキストファイル(変数FN)をStream内に読み込んでいます。
102行目「MsgBox .ReadText」では、そのStream内のデータを取り出してメッセージ出力しています。なおStreamはLoadFromFileで読み込んだままの状態なので、Position位置は先頭(Position=0)にあるため、ReadTextでは「全データ」を読み出す事ができます。

下記は図29のコードで、「AアあBC」というテキストが入っているファイルをLoadFromFileで読み込み、そのStream内のデータをReadTextで取り出しています。
LoadFromFileでファイルを読み込み、ReadTextで取得
図30

3-3.SaveToFile

SaveToFileメソッドは、Streamの内容を「ファイルに書き出し」ます。書き出す時の文字コードは、CharSetで指定したStreamの文字コードです。構文は以下になります。
Stream.SaveToFile FileName [, SaveOptions]
2つのパラメータの内容は以下です。
パラメータ内容
1FileName必須保存先のパス+ファイル名
2SaveOptions省略可adSaveCreateNotExist
(値=1)既定
指定ファイルが存在しない場合は新作
指定ファイルが存在する場合はエラー
adSaveCreateOverWrite
(値=2)
指定ファイルが存在しない場合は新作
指定ファイルが存在する場合は上書き(元データは消える)
図31

第1パラメータのパス名を省略すると、既定のドキュメントフォルダ「C:¥Users¥[User名]¥Documents¥」の場所にあるファイルと見なされるようです。
第2パラメータに「値=1を指定(=省略)」した場合に既存のファイルが存在すると「ファイルへ書き込めませんでした」との実行時エラーとなります。
なお、第2パラメータに「値=2を指定」した場合に既存のファイルが存在すると、書き出す先のファイルに元々書かれていたデータは全て削除され、上書きしたデータのみが保存されます。
SaveToFileでは、StreamのPosition位置がどこにあろうとも「Streamの内容が全てファイルに出力」されます。また出力後は、Position位置は先頭(Position=0)に移動します。
SaveToFileメソッドを使用したコード例は以下になります。
  1. '========== ⇩(6) Streamの内容をファイルに出力 ============
  2. Private Sub test04()
  3.  Dim ADOst As Object
  4.  Const FN As String = "Test_SJIS.txt"
  5.  Set ADOst = CreateObject("ADODB.Stream")
  6.  With ADOst
  7.   .type = 2
  8.   .Charset = "Shift-JIS"
  9.   .Open
  10.    .WriteText "AアあBC"
  11.    .SaveToFile FN, 2
  12.   .Close
  13.  End With
  14.  Set ADOst = Nothing
  15. End Sub
図32

131行目「.WriteText "AアあBC"」では、文字列「"AアあBC"」をStreamに書き込んでいます。
132行目「.SaveToFile FN, 2」では、ファイル(変数FN)にStreamの内容を書き込んでいます。書き込むテキストの文字コードは、128行目「.Charset = "Shift-JIS"」で指定したShift-JISになります。
また下図のように、StreamオブジェクトのPositionがどこにあっても「全データが出力」され、且つ出力後はPositionが先頭に移動します。
SaveToFile実行後はPositionは先頭に移動
図33

3-4.ReadText

ReadTextメソッドは、Streamオブジェクトから「テキストデータ」を読み取り、戻り値として出力します。なお読み取る範囲は「現在位置(Position値)から後ろ側」となります。構文は以下です。
 Stream.ReadText( [NumChars] ) As String
引数(NumChars)には以下を指定します。
定数内容
adReadAll-1全てを読み取る(既定)
adReadLine-21行を読み取る
-Long型の値読み取る文字数
 尚、読み取り開始位置はPositionの位置
図34

ReadTextで読み取った後のPositionは「読み取った文字列の最後」に移動します。つまり、以下のように分かれます。
 ・全データの読み取り時は、Positionは文末に移動(.Position=.Size、.EOS=True」
 ・行単位での読み取り時は、改行マークの次(=次行の先頭)、または文末に移動
 ・文字数単位での読み取り時は、読み取った文字列の最後に移動
まず「adReadAll(値= -1:既定)」の場合は、現在の読み書き開始位置(Position値)からStreamの最後までを読み取り、読み取ったテキストをString型として戻します。コード例は以下になります。
  1. '========== ⇩(7) ReadTextで全てを読み取る ============
  2. Private Sub test05()
  3.  Dim ADOst As Object
  4.  Const FN As String = "Test_SJIS.txt"
  5.  Set ADOst = CreateObject("ADODB.Stream")
  6.  With ADOst
  7.   .type = 2
  8.   .Charset = "Shift-JIS"
  9.   .Open
  10.    .LoadFromFile FN
  11.    MsgBox .ReadText(-1)
  12.   .Close
  13.  End With
  14.  Set ADOst = Nothing
  15. End Sub
図35

161行目「.LoadFromFile FN」でテキストファイルからデータをStream内に取り込み、そのデータを162行目「.ReadText(-1) 」で全て読み出し、メッセージとして出力しています。
なお、ファイルからLoadFromFileで取り込んだ直後は、読み書き開始位置はデータ先頭にあります(Position = 0)ので、Streamの全データが一度に出力されることになります。
以下の図は、改行を含んだテキストを読み込み、ReadText(-1)で出力する様子です。
ReadTextで全データを出力
図36

上図の場合は「Position値が先頭」にあるため、全データが出力されます。またReadText実行後のPosition値は文末(=EOS)に移動します。
なお、ReadText実行前にPosition値を移動した場合には、その位置から文末までが出力されます。
次に「adReadLine(値= -2)」の場合は、読み書き開始位置(Position値)から、その行の終わりまでを読み取り、読み取ったテキストをString型として戻します。この時、改行マークは戻しません。コード例は以下になります。
  1. '========== ⇩(8) ReadTextで1行ずつ読み取る ============
  2. Private Sub test06()
  3.  Dim ADOst As Object
  4.  Const FN As String = "Test_SJIS.txt"
  5.  Set ADOst = CreateObject("ADODB.Stream")
  6.  With ADOst
  7.   .type = 2
  8.   .Charset = "Shift-JIS"
  9.   .LineSeparator = -1
  10.   .Open
  11.    .LoadFromFile FN
  12.    Do Until .EOS = True
  13.     MsgBox .ReadText(-2)
  14.    Loop
  15.   .Close
  16.  End With
  17.  Set ADOst = Nothing
  18. End Sub
図37

189行目「.LineSeparator = -1」では、Stream内の改行マークとしてCrLf(値=-1:既定)を指定しています。
192行目「.LoadFromFile FN」でテキストファイルからデータをStream内に取り込みます。
195行目「.ReadText(-2) 」では、「現在の読み書き開始位置から改行マーク直前まで」のデータを取り出し、メッセージとして出力しています。ですので「改行マークでテキストを分割」して「連続して出力」をするために、194行目「Do Until .EOS = True」で文末(EOS=True)が来るまで195行目を繰り返しています。
ファイルからLoadFromFileで取り込んだ直後は、読み書き開始位置(Position値)はデータ先頭(Position = 0)にありますが、改行マークまでの1行分を取り出した後は、「改行マークのすぐ後ろ側(=次の行頭)」または「文末」に移動します。
そのため再びReadText(-2)が実行された際には、次の行頭から行末までのデータが取り出されます。
以下の図は、改行を含んだテキストを読み込み、ReadText(-2)で各行データを出力する様子です。
最も右側の「EOS=True」の状態まで達すると、出力するデータは無いのでDo~Loopを抜け出すようにしています。
ReadTextで1行ずつデータを出力
図38

なお1行ずつ読み取る場合に「その行の読み取りをスルー」したい場合には、下で説明する「SkipLine」メソッドを使います。
ReadTextメソッドの引数に「文字数」を指定した場合には、「現在の読み書き開始位置(Position位置)から指定文字数分」のデータを取り出します。コード例は以下になります。ここでは「読み書き開始位置を2バイト後ろに移動」させ、「7文字分」を出力させています。
  1. '========== ⇩(9) ReadTextで文字数を指定して読み取る ============
  2. Private Sub test07()
  3.  Dim ADOst As Object
  4.  Const FN As String = "Test_SJIS.txt"
  5.  Set ADOst = CreateObject("ADODB.Stream")
  6.  With ADOst
  7.   .type = 2
  8.   .Charset = "Shift-JIS"
  9.   .Open
  10.    .LoadFromFile FN
  11.    .Position = 2
  12.    MsgBox .ReadText(7)
  13.   .Close
  14.  End With
  15.  Set ADOst = Nothing
  16. End Sub
図39

221行目「.LoadFromFile FN」でファイルを読み込んだ直後は、Positionは先頭にあるのですが、222行目「.Position = 2」で2バイト後方に移動させています。お気付きの様に、移動させる単位が「バイト」なので、文字の切れ目を狙うとすれば「Streamに設定された文字コードによって異なる」事になります(図19参照)。
223行目「MsgBox .ReadText(7)」では、「Position位置から、指定した7文字」を取り出してメッセージとして出力します。この文字数の中には改行マークも含まれ、今回のCrLfだと2文字とカウントされます。
この図39の動きを図にすると以下のようになります。ここでは、改行と全角を含んだ文字列をファイルから読み込んでいます。
出力後のPositionの位置は、読み取った文字の最後(スタート位置+指定文字数)の位置となります。
ReadTextで文字数を指定してデータを出力
図40

Streamから文字列を切り出す場合、図39のように「Position」+「ReadText(文字数)」を使用すると、文字コードを考慮しなければならなくなるのはデメリットです。
そこで、一旦「VBA内の文字列」にしてから「VBAのMid関数など」を使って文字列を切り出す手法が以下です。条件は図39と同じです。
  1. '========== ⇩(10) 全データからMid関数等を使って切り出す ============
  2. Private Sub test08()
  3.  Dim ADOst As Object
  4.  Const FN As String = "Test_SJIS.txt"
  5.  Dim Str As String
  6.  Set ADOst = CreateObject("ADODB.Stream")
  7.  With ADOst
  8.   .type = 2
  9.   .Charset = "Shift-JIS"
  10.   .Open
  11.    .LoadFromFile FN
  12.    Str = .ReadText(-1)   '全データを取得
  13.    MsgBox Mid(Str, 3, 7)   '文字列として切り出す
  14.   .Close
  15.  End With
  16.  Set ADOst = Nothing
  17. End Sub
図41

252行目「.LoadFromFile FN」でテキストファイルを読み込んだ後、253行目「Str =.ReadText(-1)」で全データを変数Strに代入しています。
変数Strにはデータは「文字列」として格納されていますので、254行目「MsgBox Mid(Str, 3, 7)」のように使い慣れたVBAのMid関数などを使用することで、文字列を切り出す事が可能です。
尚この場合でも、改行マーク(CrLf)は2文字とカウントします。

3-5.WriteText

WriteTextメソッドは、文字列をPositionの位置からStreamに書き込みます。構文は以下です。
 Stream.Writetext Data [,Options]
2つのパラメータの内容は以下になります。
パラメータ内容
1Data必須Streamに書き込む文字列
2Options省略可
定数内容
adWriteChar0第1引数の文字列のみ(既定)
adWriteLine1第1引数の文字列の後ろに改行を加える
(=LineSeparatorで指定の改行マーク)
図42

第1パラメータ(Date)には、Streamに書き込む文字列(変数の場合はString型)を指定します。
第2パラメータ(Options)には、書き込む文字列の後ろに「改行マーク」を付けるか否かの設定です。既定(省略した場合)は改行を付けませんが、adWriteLine(値=1)を指定した場合は「LineSeparatorプロパティで設定した改行マーク」を1つ追加します。
なお、WriteTextで文字をStreamに書き込んだ後は、Position位置は「書き込んだ文字の後ろ側」に移動します。
WriteTextを使ったコード例は以下です。Streamに「"abcdefg"」を書き込み、行末に改行を入れます。続けて「"ABC"」を書き込みます。
  1. '========== ⇩(11) StreamにWriteTextでデータを書き込む ============
  2. Private Sub test09()
  3.  Dim ADOst As Object
  4.  Set ADOst = CreateObject("ADODB.Stream")
  5.  With ADOst
  6.   .type = 2
  7.   .Charset = "Shift-JIS"
  8.   .Open
  9.    .WriteText "abcdefg", 1
  10.    .WriteText "ABC", 0
  11.    .Position = 0
  12.    MsgBox .ReadText(-1)
  13.   .Close
  14.  End With
  15.  Set ADOst = Nothing
  16. End Sub
図43

280行目「.WriteText "abcdefg", 1」で1行目の文字列を書き込み、最後に改行を入れています。
WriteTextでStreamに書き込んだ後は、Position位置は「書き込んだ文字列の最後」に移動しますので、WriteTextを連続して実行することで文字列が、後ろに後ろにと追加されます。
281行目「.WriteText "ABC", 0」では、2行目の文字列を書き込んでいます。こちらには改行を入れていません。
データを連続してStreamに書き込んだ後は、Position位置は文末になります。このStreamデータをReadTextで読み取る際には「Positionよりも後方が対象」となりますので、282行目「.Position = 0」で先頭にPositionを移動させてから283行目「MsgBox .ReadText(-1)」で読み出しています。
図で表すと以下のようになります。
WriteTextでStream内に書き込み
図44

このように連続してWriteTextを実行すれば何の問題は無いのですが、途中で異なるメソッドを実行させる時には「Positonの位置」に注意が必要です。例えば、途中でSaveToFileでファイルにテキストを保存する場合が以下のコードです。
  1. '========== ⇩(12) WriteTextでデータを書き込む前にPositionを変更 ============
  2. Private Sub test10()
  3.  Dim ADOst As Object
  4.  Const FN As String = "Test_SJIS.txt"
  5.  Set ADOst = CreateObject("ADODB.Stream")
  6.  With ADOst
  7.   .type = 2
  8.   .Charset = "Shift-JIS"
  9.   .Open
  10.    .WriteText "abcdefg", 1
  11.    .SaveToFile FN, 2
  12.    .WriteText "ABC", 0
  13.    .Position = 0
  14.    MsgBox .ReadText(-1)
  15.   .Close
  16.  End With
  17.  Set ADOst = Nothing
  18. End Sub
図45

311行目「.WriteText "abcdefg", 1」でStreamに文字を書き込んだ後、312行目「.SaveToFile FN, 2」で一旦Stream内容をファイルに書き込んでいます。その後再び313行目「.WriteText "ABC", 0」でStreamに文字を書き込んでいます。
SaveToFile」でも説明したように「SaveToFile実行後は、Positionは先頭に移動」します。一方でWriteTextはPositionの位置から書き込みを始めますので、Streamの先頭から上書きしていく形になります。
図で示すと以下のようになります。
Positionを変えながらWriteTextでStream内に書き込み
図46

図46の一番右側では、先頭部が重ね書きされてしまっています。今回はShift-JISなので、半角文字=1バイトでしたが、全角(2バイト)が混ざっていると文字化けが発生する可能性もあります。またUTF-8だと1文字当たりのバイト数が1~4バイトと幅があるので、文字化けする可能性も高まります。
なお先頭から上書きする場合でも、WriteTextで書き込む文字の量(Size)がStreamの既存文字量(Size)以上であれば、全てが置き換わる事になります。
もしSaveToFile等でPositionが移動してしまう場合は、「.Position = .Size」でPositionを文末に移動させるか、SetEOSメソッドで「Streamをクリア」した後、作業を続ける必要があります。LoadFromFileの場合もファイル読み込み後はPositionは先頭にありますので、そのままWriteTextを使うと「先頭から上書き」となる為、同様の注意が必要です。
なお図46の場合、最終的なテキストの末尾にはCrLfが付いているのですが、なぜかメッセージBoxの最後には「改行が表現されていない」事が分かります。MsgBox関数の仕様なのでしょうが、この現象について詳しい事は分かりませんでした。

3-6.Read

ReadメソッドはStreamから「バイトデータ」を読み取り、戻り値として「Byte型の配列」を返します。なおこのReadメソッドを使う際は、Typeプロパティ値をadTypeBinary(Type = 1)にしておく必要があります。構文は以下です。
 Stream.Read(Numbytes) As Variant
引数(Numbytes)には、以下を指定できます。なお、読み取りスタート位置は「Position位置」です。
定数内容
adReadAll-1全バイトを読み取る(既定)
adReadLine-21行を読み取る
-Long型の値読み取るバイト数
図47

Microsoftサイトを含む他のサイトでは「1行分を読み取る(値=-2)」が有効であるかのような説明をしています。しかし指定すると実行時エラーが発生するので、図47では見え消しにしています。
Readメソッドを使ったコード例は以下です。テキストファイルから文字列を読み込み、Streamから1バイトずつ取得してその値を16進数でイミディエイトWindowに出力しています。謂わば、読取専用の「バイナリエディタ」みたいな役目を果たします。
  1. '========== ⇩(13) Streamの各バイト値をReadで読み取る ============
  2. Private Sub test11()
  3.  Dim ADOst As Object
  4.  Dim Str() As Byte    '←Readの戻り値を入れる配列
  5.  Const FN As String = "Test_SJIS.txt"
  6.  Dim i As Long
  7.  Set ADOst = CreateObject("ADODB.Stream")
  8.  With ADOst
  9.   .type = 1
  10.   .Open
  11.    .LoadFromFile FN
  12.    Str = .Read(-1)
  13.    For i = LBound(Str, 1) To UBound(Str, 1)
  14.     Debug.Print WorksheetFunction.Dec2Hex(Str(i), 2) & vbTab;
  15.    Next i
  16.   .Close
  17.  End With
  18.  Set ADOst = Nothing
  19. End Sub
図48

まず339行目「.type = 1」で、Streamを「バイナリ型」で管理させます。
342行目「.LoadFromFile FN」では、Shift-JISのテキストファイルからデータをStreamに取り込んでいます。ファイルを取り込んだだけなので、Positionは先頭にあります。
343行目「Str = .Read(-1)」では、Positionの位置から文末まで(今回の場合は、Streamの全バイト)を読み取り、そのバイナリデータを「Byte型の配列」として戻してきます。そのため333行目「Dim Str() As Byte」でByte型の動的配列を宣言しています。なお動的配列のサイズを事前に決定しておく必要は無く、343行目で読み取ったバイト数と同じ要素数の配列に自動的にサイズ変更されます。
図48のコードの流れを図で表すと、以下のようになります。ここでは、全角・改行を含んだテキストを読み込ませています。
Stream内からReadで読み込む
図49

なお今回は「複数バイト」を読み取るために「配列」が必要なようにも見えますが、単一の「1バイトのみ」の読み取りの場合でも「Byte型の動的配列宣言」+「動的配列に読み取りバイト値を戻す」という図48と同様の手順が必要です。1バイトのみの場合は「1要素の配列」となります。
読み取ったバイト値が入った配列の処理ですが、今回は345~347行目のFor~Nextで、1バイトずつ(=1要素ずつ)値をイミディエイトWindowに出力させています。1要素に入っているバイト値は1バイト分なので、値はByte型の「0~255(10進数)」ですが、図49の左下のバイナリエディタの値(=16進数)と合わせるために、10進数→16進数の変換をしています。
その変換部分が、346行目の「WorksheetFunction.Dec2Hex(Str(i), 2)」です。
10進数→16進数の変換方法としては、VBAの「Hex関数」も存在します。しかし変換後の2桁目がゼロの場合(0x00~0x0F)は、「1桁目の値(0~9,A~F)」しかHex関数は戻しません。例えば改行のCrLfは「0x0D」「0x0A」ですが、「D」「A」しか戻してくれないのです。
この2桁表示方策としては、以下のような方法が良く紹介されています。
 ・「 = Right("0" & HEX(10進数), 2)」のように先頭に"0"を付け、右から2文字を切り出す。
もちろんこの方法でも良いのですが、Excel VBAのWorksheetFunctionには表示桁数を指定できるDec2Hex関数がありますので、ここではそれを使用しています。
なおセル内で使用する関数「Dec2Hex」を使って「Evaluate("Dec2Hex( " & Str(i) & ",2)")」としてもOKです。

3-7.Write

Writeメソッドは、Streamにバイトデータを書き込みます。なお、Writeを使用するにはTypeプロパティ値をadTypeBinary(Type = 1)にしておく必要があります。構文は以下です。
 Stream.Write Buffer
指定するパラメータ(Buffer)は以下になります。
パラメータ内容
Buffer必須書き込むバイト配列の入ったVariant型の値を指定
図50

パラメータの内容がピンと来ないと思いますので、Writeを使ったコード例を以下に示します。
Streamに対し「"abあい" + 改行」と「"12"」のバイト値を連続して書き込み、最後にファイルに出力しています。
  1. '========== ⇩(14) StreamにWriteでバイト値を書き込む ============
  2. Private Sub test12()
  3.  Dim ADOst As Object
  4.  Dim Str() As Byte
  5.  Const FN As String = "Test_UTF16.txt"
  6.  Set ADOst = CreateObject("ADODB.Stream")
  7.  With ADOst
  8.   .type = 1
  9.   .Open
  10.    Str = "abあい" & vbCrLf
  11.    .Write Str
  12.    Str = "12"
  13.    .Write Str
  14.    .SaveToFile FN, 2
  15.   .Close
  16.  End With
  17.  Set ADOst = Nothing
  18. End Sub
図51

373行目「Dim Str() As Byte」では、Readの時と同様に「Byte型の動的配列」を宣言しています。
378行目「.type = 1」では、Streamをバイナリ型で管理しています。
381行目「Str = "abあい" & vbCrLf」では、文字列「"abあい"+改行」をバイト値として配列Strに格納しています。VBA内では文字列は「UTF-16LE」で管理されていますので、半角全角を問わず「2バイト/文字」となります。
382行目「.Write Str」では、381行目で作成したByte型の配列StrをStreamに書き込んでいます。
384~385行目も同様に、Streamに「"12"」の文字列のバイト値を書き込んでいます。
387行目「.SaveToFile FN, 2」では、Streamのデータをファイルに出力しています。図51のコードの流れを図で示すと、以下のようになります。
StreamにWriteで書き込む
図52

書き込んだテキストファイルは、メモ帳の右下にも表示されている通り、「UTF-16LE」の文字コードで保存されています。つまりStream内には「UTF-16LE」として文字列のバイト値が書き込まれている事が分かります。なお、テキスト型のUTF-16LEでStreamが管理されている訳では無いので、先頭にはBOMはありません。
今回は「文字列→バイト値」に変換してからStreamに書き込んでいますが、直接バイト値(0~255)を書き込んでもOKです。

3-8.SkipLine

SkipLineメソッドは次の改行マークまでの1行分の文字を飛び越え(スキップ)ます。構文は以下で、パラメータはありません。
 Stream.SkipLine
このメソッドは、Streamが既定のテキスト型(Type=2 )の時に使用できます。また行の終わりを示す改行マークはLineSeparatorプロパティ値で判断されます。
SkipLineについて別な言い方をすれば「次に出現する改行マークの後ろまでPositionを移動」させる機能です。ですので行毎に読み取り(ReadText(-2) )をする場合だけでなく、各行の行頭部分だけを取り出したり、タイトルを除いた全行の取り出しなどにも使えます。
以下に示したSkipLineを使ったコード例では、タイトル行のみスキップし、それ以降を1行ずつ取り出しています。
  1. '========== ⇩(15) SKipLineで読み込み行を制御する ============
  2. Private Sub test13()
  3.  Dim ADOst As Object
  4.  Const FN As String = "Test_SJIS.txt"
  5.  Dim i As Integer
  6.  Dim Data As Variant    '←カンマで分割したデータを入れる配列
  7.  Set ADOst = CreateObject("ADODB.Stream")
  8.  With ADOst
  9.   .type = 2
  10.   .Charset = "Shift-JIS"
  11.   .LineSeparator = -1
  12.   .Open
  13.    .LoadFromFile FN
  14.    .SkipLine
  15.    Do Until .EOS = True
  16.     Data = Split(.ReadText(-2), ",")
  17.     Debug.Print Data(0), Data(1), Data(2)
  18.    Loop
  19.   .Close
  20.  End With
  21.  Set ADOst = Nothing
  22. End Sub
図53

今回読み込むファイルは、下図の左側のように「1行目タイトル行+2行目以降にデータ」が並んでいるものとし、1行には「3つのデータがカンマ区切り」で並んでいます。
414行目「.LoadFromFile FN」で、そのファイルを読み込み、まず415行目「.SkipLine」で1行スキップします。このことで「StreamのPositionは、2行目の先頭に移動」することになります。
417~420行目のDo~Loopでは、Streamの末尾(EOS)が来るまで「1行ずつ」処理を行っています。コードの流れを図で表したのが下図です。
StreamにWriteで書き込む
図54

Do~Loop内の処理内容をもう少し詳しく説明します。415行目で1行目はスキップしていますので、処理は2行目から最後までという事になります。
まず418行目「Data = Split(.ReadText(-2), ",")」では、「.ReadText(-2)」で1行分を読み取ります。1行は3つのデータがカンマで区切られていますので、「カンマを目印」にしてSplit関数で分割します。分割されたデータは配列(要素番号はゼロ始まり)になりますので、Variant型の変数Dataで受け取ります。
419行目「Debug.Print Data(0), Data(1), Data(2)」では、3つの配列要素をイミディエイトWindowに出力しています。

3-9.SetEOS

SetEOSメソッドは、Streamの末尾の位置(EOS)を、現在のPositionプロパティの位置に設定するものです。
構文は以下で、設定パラメータはありません。
 Stream.SetEOS
上記説明では良く分からないと思うので、下図を例に説明します。Shift-JISのテキストがStreamに入っているとします。
図55の①はSize=6バイトのStreamですが、Positon=4に設定した後「SetEOSメソッド」を実行すると、②のようにPositionの位置がEOS(Streamの末尾)となります。この時、Position=4よりも後ろにあったデータ(ここでは「"B"」と「"C"」)は削除されます。
また③のように、Position=0に設定した後「SetEOSメソッド」を実行すると、Streamのサイズはゼロ(=空の状態)となります。
SetEOSの動作
図55

不要部分を削除したり、StreamオブジェクトをCloseせずに空にする場合には必要な機能です。

3-10.CopyTo

CopyToメソッドは、Streamの内容を別のStreamにコピーします。構文は以下です。
 Stream.CopyTo DestStream [, NumChars]
パラメータは2つあり、以下の内容になります。
引数内容
1DestStream必須コピー先のStreamオブジェクト
2NumChars省略可 コピー範囲の指定
Type指定値内容
1
(バイナリ)
-1(既定)全バイトをCopy
0以上の整数指定バイト数をCopy
2
(テキスト)
-1(既定)全文字をCopy
0以上の整数指定文字数をCopy
但し、Positionの位置以降が対象
図56

第1パラメータ(DestStream)は、コピー先のStreamオブジェクトです。このCopyToメソッドを実行する時点では、コピー先StreamはOpenメソッドで開かれている必要があります。
なおMicrosoftのサイトでは、基本的には同じ型のStream同士(テキスト型→テキスト型 または バイナリ型→バイナリ型)でのデータコピーを推奨していますが、図61のようにBOM削除などの時には「テキスト型→バイナリ型」を使う事は可能です。但し「バイナリ型→テキスト型」では実行時エラーが発生します。
第2パラメータ(NumChars)は、コピーする範囲で、「-1」を指定(または省略)すれば、全データをコピーする事になります。但しコピー元StreamのPosition位置から後方が対象です。
「整数値」を指定した場合には、指定した文字数(またはバイト数)をコピーします。この時もコピー元のStreamのPosition位置から後方が対象です。なお、指定した文字数(またはバイト数)が、Stream末尾を超えた値の場合は、末尾までを指定(= -1を指定したの同等)した事になります。
まず基本的な動きを下図で説明します。CopyToを使ってStream1(上段)の内容をStream2(下段)にコピーしています。
なおCopyToのパラメータは、①②が「-1(全データコピー)」、③が「指定文字数(またはバイト数)指定」としています。
CopyToの基本的な動き
図57

図57の左側①は、コピー先のStream2が空の場合です。Stream1のPosition位置から末尾までのデータがStream2にコピーされます。ここではPosition=0となっているため、全データがコピーされます。
なお、CopyTo実行後のコピー元(Stream1)のPositionは、コピーしたデータ範囲の最後に移動します。ここでは全データ(パラメータに「-1」を指定)が範囲なので、Stream1の末尾になります。コピー先(Stream2)の実行後のPosition位置は、データ貼付け範囲の末尾となります。
図57の中央②は、コピー先のStream2にはデータが入っている場合です。Stream2のPosition位置は末尾にありますので、Stream1のデータがStream2の末尾に追加される形となります。コピー元・コピー先のCopyTo実行後のPosition位置は、データ末尾になります。
図57の右側③は、コピー元・コピー先ともPosition位置がデータの中間に居る場合で、且つコピー範囲が指定文字数(または指定バイト数)の場合です。ここでは1文字を指定しています。
まずコピー元(Stream1)のPositionは「"BC"」の直前にあり、且つコピーは1文字なので、コピー対象は「"B"」の1文字ということになります。一方コピー先(Stream2)のPositionは「"YZ"」の直前にあります。この状態でCopyTo(1)を実行すると、コピー先(Stream2)の「"Y"」の部分をStream1の「"B"」で置き換えますが、Stream2の最後の「"Z"」はそのまま残ります。
またコピー先のPositionも、置き換えた「"B"」の直後に移動しますし、コピー元も「"B"」だけコピーしたためPositionは「"B"」の直後となります。
CopyToでの動きを整理すると、以下のようになります。
パラメータCopy範囲/貼付け範囲Positionの移動先
Copy元-1Position~末尾末尾
文字数Position~指定文字数分Copy範囲の後ろ
Copy先-1Position~Copy範囲分貼付け範囲の後ろ
文字数
図58

なおPositionの値はバイト単位なので、そのStreamの文字コードを把握し、並んでいる文字の1つ1つが何バイトなのか、またBOMの有無まで考慮しないと、目的の文字位置(=Position値)を設定できないことに注意して下さい。
また図58はテキスト型(Type=2)の表となっていますが、バイナリ型(Type=1)の場合はPosition値もコピーする個数もバイト単位となります。
CopyToメソッドを使ったコード例を3種類紹介します。
まず下記は、ファイルの文字コードを変換するコードです。CopyToメソッドでStream内容をコピーすると「コピー元の文字コードからコピー先の文字コードに変換」される機能を使っています。ここではShift-JISのテキストをUTF-16LEに変換します。
  1. '========== ⇩(16) CopyToで文字コードを変換 ============
  2. Private Sub test14()
  3.  Dim ADOst_SJIS As Object
  4.  Const FN_SJIS As String = "Test_SJIS.txt"   '←Shift-JISファイル
  5.  Dim ADOst_UTF16 As Object
  6.  Const FN_UTF16 As String = "Test_UTF16.txt"   '←UTF-16LEファイル
  7.  Set ADOst_UTF16 = CreateObject("ADODB.Stream")
  8.  ADOst_UTF16.type = 2
  9.  ADOst_UTF16.Charset = "Unicode"   '←UTF-16LE
  10.  ADOst_UTF16.Open
  11.   Set ADOst_SJIS = CreateObject("ADODB.Stream")
  12.   With ADOst_SJIS
  13.    .type = 2
  14.    .Charset = "Shift-JIS"   '←Shift-JIS
  15.    .Open
  16.     .LoadFromFile FN_SJIS
  17.     .CopyTo ADOst_UTF16, -1
  18.    .Close
  19.   End With
  20.   ADOst_UTF16.SaveToFile FN_UTF16, 2   '←UTF-16LEファイルとして出力
  21.  ADOst_UTF16.Close
  22.  Set ADOst_SJIS = Nothing
  23.  Set ADOst_UTF16 = Nothing
  24. End Sub
図59

448~451行目では、UTF-16LEのStreamオブジェクトをOpenしています。この「ADOst_UTF16オブジェクト(UTF-16LEのStream)」は、459行目での「データのコピー先」となります。
453~457行目では、Shift-JISのStreamオブジェクトをOpenしています。こちらはデータの送り先です。
458行目「.LoadFromFile FN_SJIS」では、Shift-JISのテキストファイルからStream内にデータを読み込んでいます。
459行目「.CopyTo ADOst_UTF16」では、Shift-JISのStreamのデータをUTF-16LEのStreamにコピーしています。Shift-JISのStreamのPositionは、ファイルを読み込んだだけですので先頭(Position=0)に居ますし、CopyToメソッドの第2パラメータを「-1」としている為、全データのコピーとなります。
コピーされたデータはUTF-16LEに変更され、且つ図12でも分かるようにBOM付です。
463行目「ADOst_UTF16.SaveToFile FN_UTF16, 2」では、「UTF-16LEのStream」のデータをファイルに書き出しています。 コピー前後のテキストファイルは以下のようになります。
文字コードの変換
図60

次は、BOM付のファイルをBOM無しのファイルに変換するコードです。対象はBOMが有りうる「UTF-8 、UTF-16LE/BE」になります。ここでは、UTF-8(BOM付)のファイルをUTF-8(BOM無し)ファイルに変換しています。
  1. '========== ⇩(17) BOM付ファイルをBOM無しに変換 ============
  2. Private Sub test15()
  3.  Dim ADOst_Txt As Object
  4.  Const FN_Txt As String = "Test_UTF8.txt"   '←BOM付
  5.  Dim ADOst_Bin As Object
  6.  Const FN_Bin As String = "Test_UTF8N.txt"   '←BOM無し
  7.  Set ADOst_Bin = CreateObject("ADODB.Stream")
  8.  ADOst_Bin.type = 1
  9.  ADOst_Bin.Open
  10.   Set ADOst_Txt = CreateObject("ADODB.Stream")
  11.   With ADOst_Txt
  12.    .type = 2
  13.    .Charset = "UTF-8"
  14.    .Open
  15.     .LoadFromFile FN_Txt
  16.     .Position = 3     '←UTF-8はBOMは3バイト(UTF-16は2バイト)
  17.     .CopyTo ADOst_Bin, -1
  18.    .Close
  19.   End With
  20.   ADOst_Bin.SaveToFile FN_Bin, 2   '←BOM無しファイルとして出力
  21.  ADOst_Bin.Close
  22.  Set ADOst_Txt = Nothing
  23.  Set ADOst_Bin = Nothing
  24. End Sub
図61

487~489行目で、バイナリ型としてStreamを開きます。これをオブジェクト変数「ADOst_Bin」としています。
491~495行目で、UTF-8(BOM付)のテキスト型としてStreamを開きます。こちらは「ADOst_Txt」としています。
496行目「.LoadFromFile FN_Txt」で、UTF-8(BOM付)のテキストファイルを読み込みます。
文字コードUTF-8のBOMは3バイト(0xEF BB BF:「文字コード編」参照)ですので、497行目「.Position = 3」で読み取り開始点を3バイト後ろに移動させます。この位置がCopyToの起点になります。
498行目「.CopyTo ADOst_Bin, -1」で、「BOMを除いたデータ」をバイナリ型Streamにコピーします。
バイナリ型Streamに格納されたデータはバイナリとして管理されていますが、内容は「BOMを除いたUTF-8のデータ」です。
このデータを502行目「ADOst_Bin.SaveToFile FN_Bin, 2」で、テキストファイルに書き込みます。
書き込み前後の形は下図のようになります。左が「UTF-8(BOM付)」、右が「UTF-8(BOM無)」です。テキストは全く同じですが、下側のバイナリデータを見ると「BOM(0xEF BB BF)」が削除されている事が分かります。
BOM付からBOM無しへの変換
図62

なお、498行目「.CopyTo ADOst_Bin, -1」では「テキスト型Stream → バイナリ型Stream」へのCopyToメソッドでのコピーは出来ますが、その逆を行おうとすると「操作は許可されていない」との実行時エラーが発生します。
3つ目は、テキストファイルを結合するコードです。図59で「文字コードの変換」が出来るのと同様に、異なる文字コードファイルの結合も可能です。
下ではShift-JISのテキストの末尾にUTF-16のテキストを結合(=追加)し、Shift-JISファイルに戻しています。
  1. '========== ⇩(18) テキストファイルの結合 ============
  2. Private Sub test16()
  3.  Dim ADOst1 As Object
  4.  Const FN1 As String = "Test_SJIS.txt"
  5.  Dim ADOst2 As Object
  6.  Const FN2 As String = "Test_UTF16.txt"
  7.  Set ADOst1 = CreateObject("ADODB.Stream")
  8.  ADOst1.type = 2
  9.  ADOst1.Charset = "Shift-JIS"
  10.  ADOst1.Open
  11.   ADOst1.LoadFromFile FN1
  12.   ADOst1.Position = ADOst1.Size    '←Positionを末尾に移動
  13.   Set ADOst2 = CreateObject("ADODB.Stream")
  14.   With ADOst2
  15.    .type = 2
  16.    .Charset = "Unicode"
  17.    .Open
  18.     .LoadFromFile FN2
  19.     .CopyTo ADOst1, -1   '←末尾に追加
  20.    .Close
  21.   End With
  22.   ADOst1.SaveToFile FN1, 2
  23.  ADOst1.Close
  24.  Set ADOst2 = Nothing
  25.  Set ADOst1 = Nothing
  26. End Sub
図63

527~530行目で、Shift-JISのテキスト型でStreamを開きます。これを「ADOst1」オブジェクトとしています。
531行目「ADOst1.LoadFromFile FN1」でShift-JISのテキストファイルを読み込みます。
読み込んだだけではPositionは先頭に居ます。今回は「末尾にテキストを追加」しますので、532行目「ADOst1.Position = ADOst1.Size」でPositionを末尾に移動させます。
534~538行目で、Unicode(=UTF-16LE(BOM付))のテキスト型でStreamを開きます。
539行目「.LoadFromFile FN2」で、UTF-16LEのテキストファイルを読み込みます。
540行目「.CopyTo ADOst1, -1」で、UTF-16LEのデータをShift-JISデータの末尾にコピーします。
「Shift-JISのデータ」+「UTF-16LEのデータ」という形になったShift-JISのStreamデータを、544行目「ADOst1.SaveToFile FN1, 2」でファイルに書き出します。
元の2つのファイルと、書き換えられたファイルの形は下図のようになります。左側が「Shift-JISの元ファイル」、中央が「UTF-16のファイル」、右側が「2つのテキストを結合したShift-JISファイル」です。
テキストファイルの結合
図64

図64の下側のバイナリエディタを見ると、UTF-16LEのバイトデータをそのままShift-JISの末尾に追加しているのでは無く、図59と同様に「文字コード変換」を行いながら追加しているのが分かるかと思います。
寄り道(CopyToで置き換えられる文字数)
図57等の説明では、あたかも「1文字=1バイト」のような説明図を使用しました。この図だけを見ると、例えば「Copy元の2文字をCopyToすれば、コピー先の2文字が置き換えられる」ような感覚になるかもしれません。
しかし図59図63でも分かるように、文字は「Streamの文字コードに従って処理」されて格納されるため、ほぼ
Copyする文字数 ≠ 置き換えられる文字数」となると考えた方が良いと思います。
この現象を確認するため、下記コードでは「"Aアあ"」という3文字を「"1234567890"」という10文字の先頭から上書きしています。文字コードは両方ともUTF-8です。
  1. '========== ⇩(19) Copyする文字数 ≠ 置き換えられる文字数 ============
  2. Private Sub test17() '文字列の1部を上書き
  3.  Dim ADOst1 As Object
  4.  Dim ADOst2 As Object
  5.  Set ADOst1 = CreateObject("ADODB.Stream")
  6.  ADOst1.type = 2
  7.  ADOst1.Charset = "utf-8"
  8.  ADOst1.Open
  9.   ADOst1.WriteText "1234567890"   'ADOst1は10文字
  10.   ADOst1.Position = 0
  11.   Set ADOst2 = CreateObject("ADODB.Stream")
  12.   With ADOst2
  13.    .type = 2
  14.    .Charset = "utf-8"
  15.    .Open
  16.     .WriteText "Aアあ"
  17.     .Position = 0
  18.     .CopyTo ADOst1, -1   'ADOst2の3文字("Aアあ")をCopyTo
  19.    .Close
  20.   End With
  21.   ADOst1.Position = 0
  22.   MsgBox ADOst1.ReadText   'ADOst1のStream内容を表示
  23.  ADOst1.Close
  24.  Set ADOst2 = Nothing
  25.  Set ADOst1 = Nothing
  26. End Sub
図65

569行目「ADOst1.WriteText "1234567890"」では、コピー先のStreamにデータを格納しています。置き換えるのは先頭部分ですので570行目「ADOst1.Position = 0」で、Positionを先頭に移動させています。
577行目「.WriteText "Aアあ"」では、コピー元のStreamにデータを格納しています。こちらも全データをコピーする為に、578行目「.Position = 0」で先頭にPositionを移動させています。
579行目「.CopyTo ADOst1, -1」で、「"1234567890"」の先頭部分を「"Aアあ"」で置き換えるように、データをCopyToしています。置き換えた結果は、584行目「MsgBox ADOst1.ReadText」で表示させています。
置き換え結果は、以下の左側のように「"Aアあ890"」と6文字になります。
テキストの置き換え
図66

これは、上書きされる側(ADOst1)のStreamには数字のみ(UTF-8では、数字は1バイト/文字)であるのに対し、上書きする側のStreamの「Aアあ」の内、アルファベットの「A」は1バイトですが、半角カナの「ア」はUTF-8では3バイト、全角ひらがなの「あ」も3バイトである為です。
「Aアあ」では「全7バイト」を上書きしますので、上書きされなかった文字は「10 - 7 = 3バイト」の「"890"」となる訳です。
同じ文字コードでこのような現象が発生するのはUTF-8とShift-JISだけだと思いますが、これが異なる文字コードの間で行うと、かなり計算が面倒になります。
空のStreamにCopyToをしたり、末尾にCopyToしたりする時には、文字数についてはあまり気にしないと思います。しかし「文字列を置き換えたい」ような場合には「Stream内容を一旦VBA内の文字列にし、文字列として処理後にStreamに入れ直す」方が安心だと思われます。
以下では、VBA内で文字列の置換をしてからStream処理をしています。
  1. '========== ⇩(20) VBAの文字列計算で上書きをする ============
  2. Private Sub test18()
  3.  Dim ADOst As Object
  4.  Dim str1 As String
  5.  Dim str2 As String
  6.  str1 = "1234567890"
  7.  str2 = "Aアあ"
  8.  str1 = str2 & Mid(str1, Len(str2) + 1)
  9. ' str1 = str2 & Mid(str1, LenB(StrConv(str2, vbFromUnicode)) + 1)
  10.  Set ADOst = CreateObject("ADODB.Stream")
  11.  With ADOst
  12.   .type = 2
  13.   .Charset = "utf-8"
  14.   .Open
  15.    .WriteText str1
  16.    .Position = 0
  17.    MsgBox .ReadText
  18.   .Close
  19.  End With
  20.  Set ADOst = Nothing
  21. End Sub
図67

609行目「str1 = str2 & Mid(str1, Len(str2) + 1)」では、文字列str1の先頭部分を文字列str2で置き換えています。Len関数を使っているので「"Aアあ"」は3文字という計算です。
一方、見え消しをしている610行目「str1 = str2 & Mid(str1, LenB(StrConv(str2, vbFromUnicode)) + 1)」は、全角を2文字と数える方法で、「"Aアあ"」は4文字という計算です。
この数式で置き換えた文字列(str1)を617行目「.WriteText str1」でStreamに格納し、処理を行っています。
この方法でしたら比較的簡単で、後からコードを見た人にも分かり易いのではと思います。

3-11.Close

Closeメソッドは、開いているオブジェクトを閉じます。構文は以下で、パラメータはありません。
 Stream.Close
オブジェクトを閉じても「オブジェクト = Nothing」をしない限りメモリ上には残るため、再度開く(Openメソッドの実行)ことはできます。

3-12.Flush

Flushメソッドは、Streamのバッファの内容を全て吐き出す処理をします。構文は以下で、パラメータはありません。
 Stream.Flush
ファイルへのデータ書き込み中にこのメソッドを実行すると、全ての変更内容を確実に書き込むようです。但しFlushを実行しなくても更新処理は自動的に行われ、且つCloseメソッドでStreamを閉じる時にも自動的にFlushの機能が行われるので、明示的に実行しなくても良さそうです。
大量のデータを読み書きした直後の処理では、中途半端なデータ内容にならないようにFlushの実行が必要になる場合があるのかもしれませんが、Streamでは非同期処理は出来ないので、それも心配ない気がします。

3-13.Cancel

Cancelメソッドは、非同期メソッドの実行をキャンセルさせます。構文は以下で、パラメータはありません。
 Stream.Cancel
しかし今回のようなテキストファイルの読み書きでは、「非同期処理」の設定が出来ません。
つまり、Openメソッドの第3パラメータ(OpenOptions)に adOpenStreamAsync(値=1)が指定できるのであれば、非同期モードでStreamオブジェクトを開くことができるはずなのですが、「Openメソッド」の所でも紹介したように adOpenStreamUnspecified(値=-1)以外は設定できません(既定値なので、通常は設定しない)。
「ファイルの呼び出しを取り消す」などと説明をしているサイトもあるようです。しかし、100kB以上のテキストファイルをLoadFromFileで呼び出した直後にCancelも実行してみたのですが、同期状態ですので当然ながら「ファイルの読み込みが完了」してから次のコードに移っています。
非同期の設定が可能なADOのConnectionオブジェクト等のみで機能するメソッドのようです。

アプリ実例・関連する項目

CSVファイルの読み込み
CSVファイルでデータを読み書きする月間予定表

サンプルファイル

今回、説明の中で紹介したコードは、以下のサンプルファイルの標準モジュール(Module1)に記載しています。実行するにはVBEから直接実行してください。
また読み書きするテキストファイル名にはPathを指定していないので、既定のドキュメントフォルダー
「C:¥Users¥[ユーザー名]¥Documents¥」のファイルを操作対象としています。
フォルダーにファイルが無い場合にLoadFromFileを実行するとエラーとなりますので、事前にテキストファイルを作成してから試してください。その場合、ファイル名と併せて文字コードも合わせる必要があります。
テキストファイルの読み書き_ADODB.Stream編(its-050.xlsm)
セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。