-
バイナリファイルからのバイト配列への読込処理と、バイト配列からバイナリファイルへの書込処理について記します。 通常テキストファイルはバイナリファイルとは異なるものとして扱いますが、 ファイルの中身は文字コードのみで構成されたバイナリファイルとして取り扱いができます。 (データファイルは全てバイナリファイルであるとも言えますが)
■バイト配列からバイナリファイルへの書込処理
それではファイルを読込むためにファイルが存在しないと始まらないので、 バイナリファイルをバイト配列から書込む処理を説明します。
バイト配列は適当にデータを生成するとして、書込処理には System.IO.FileStream クラスの Write メソッドを使用します。Private Sub btnWrite_Click(sender As Object, e As EventArgs) Handles btnWrite.Click '書込むバイト型配列:Shift-JISの文字 "I" "J" "K" "L" "0" "1" "2" Dim arrByte As Byte() = New Byte() {&H49, &H4A, &H4B, &H4C, &H30, &H31, &H32} 'ファイルを作成して書込む (存在する場合は上書き) Dim fs As New System.IO.FileStream("sample.txt", System.IO.FileMode.Create, System.IO.FileAccess.Write) 'バイト型配列の内容をすべて書き込む fs.Write(arrByte, 0, arrByte.Length) '閉じる fs.Close() End Sub■バイナリファイルからバイト配列への読込処理
バイナリファイルからの読込処理では最初に System.IO.FileStream クラスを生成することで対象ファイルをオープンします。 System.IO.FileStream クラスの長さのプロパティ Length 分のバイト配列を宣言し Read メソッドで読込を行います。
Private Sub btnRead_Click(sender As Object, e As EventArgs) Handles btnRead.Click 'ファイルを開く Dim fs As New System.IO.FileStream("sample.txt", System.IO.FileMode.Open, System.IO.FileAccess.Read) 'ファイルを読み込むバイト型配列を作成 Dim arrByte(fs.Length - 1) As Byte 'ファイルの内容を全て読込 fs.Read(arrByte, 0, arrByte.Length) 'ファイルを閉じる fs.Close() 'バイト配列を16進数表記の文字列で表示(各要素を「-」で連結) Console.WriteLine(BitConverter.ToString(arrByte)) End Sub実行結果がコンソールには以下様に表示されます。確かに書込み処理されたデータが読み込まれています。
49-4A-4B-4C-30-31-32
■大きなバイナリファイルの分割読込処理
上記の読込処理では System.IO.FileStream クラスでオープンした後でファイルの大きさでバイト配列を宣言しましたが、 ファイルサイズが非常に大きい場合、メモリ確保に負担が掛かります。 そのため以下の様に、固定のバイト数での読込を複数回処理することで全ての読込処理が行えます。
Read メソッドは順次読込を行いますが、ファイルの最後まで読込、更にこのメソッドを呼び出すと結果として「0」が返ります。 よって、「0」が返った時点で処理を終了します。Private Sub btnRead_Click(sender As Object, e As EventArgs) Handles btnRead.Click 'ファイルを開く Dim fs As New System.IO.FileStream("sample.txt", System.IO.FileMode.Open, System.IO.FileAccess.Read) 'ファイルを16バイトずつ読み込むバイト型配列を作成 Dim arrByte(16 - 1) As Byte While True 'ファイルの内容を全て読込 Dim nRead As Integer = fs.Read(arrByte, 0, arrByte.Length) If nRead = 0 Then Exit While End If '部分読込データ処理 'バイト配列を16進数表記の文字列で表示(各要素を「-」で連結) Console.WriteLine(BitConverter.ToString(arrByte)) End While 'ファイルを閉じる fs.Close() End Sub実行結果がコンソールには以下様に表示されます。 書込まれているデータが7バイトのため、16バイト以下の読込となり1回で処理が終わることになります。
49-4A-4B-4C-30-31-32-00-00-00-00-00-00-00-00-00
■バイナリファイルのコピー処理
上記の分割読込処理を元にバイナリファイルコピーの処理を関数としてまとめてみました。
'''
''' バイナリファイルコピー ''' ''' <param name="SrcPath">コピー元のフルパスファイル名</param> ''' <param name="DesPath">コピー先のフルパスファイル名</param> ''' <param name="nSize">コピー分割処理バイト数</param> '''True:正常終了, False:NG '''エラーは詳細に処理していません Public Function CopyBinaryFile(ByVal SrcPath As String, ByVal DesPath As String, ByVal nSize As Integer) As Boolean CopyBinaryFile = True '読み書きの FileStream 宣言 Dim fsRead As System.IO.FileStream = Nothing Dim fsWrite As System.IO.FileStream = Nothing Try '読込ファイルを開く fsRead = New System.IO.FileStream(SrcPath, System.IO.FileMode.Open, System.IO.FileAccess.Read) '書込ファイルを開く fsWrite = New System.IO.FileStream(DesPath, System.IO.FileMode.Create, System.IO.FileAccess.Write) 'バイト配列宣言 Dim arrByte = New Byte(nSize - 1) {} 'コピー処理ループ While True '元のファイル読込 Dim nRead As Integer = fsRead.Read(arrByte, 0, nSize) If nRead = 0 Then Exit While End If 'コピー先に書込 fsWrite.Write(arrByte, 0, nRead) End While Catch ex As Exception 'エラー処理 Return False Finally 'ファイルクローズ If Not fsRead Is Nothing Then fsRead.Close() End If If Not fsWrite Is Nothing Then fsWrite.Close() End If End Try End Function 'バイナリファイルコピーのテスト用ボタンクリック処理 Private Sub btnCopy_Click(sender As Object, e As EventArgs) Handles btnCopy.Click Dim blnRet As Boolean = CopyBinaryFile("sample.txt", "sample2.txt", 4) Console.WriteLine("CopyBinaryFile...{0}", blnRet) End Sub関連する記事
⇒配列の使い方について(Dim, Redim)
⇒配列の使い方の注意点について(コピー, Clone)
PR -
配列から範囲を指定して要素を抽出し、新しい配列を作成する方法について記します。
それでは以下の方法を説明したいと思います。■新規配列を生成し Array.Copy でコピー
Array.Copy の引数の説明は以下の通りです。
Array.Copy(sourceArray As System.Array, sourceIndex As Integer, destinationArray As System.Array, destinationIndex As Integer, length As Integer) ・sourceArray :コピー元の配列データ。 ・sourceIndex :コピー操作の開始位置となる sourceArray 内のインデックスを表す 32 ビット整数。 ・destinationArray:コピー先の配列データ。 ・destinationIndex:格納を開始する destinationArray のインデックスを表す 32 ビット整数。 ・length :コピーする要素の数を表す 32 ビット整数。
以下のソースを見て下さい。 最初にコピー元配列「arr」を宣言し、コピー先の配列「arr2」をコピー数で領域確保しています。 今回の処理ではコピー元の位置を「6」として3個の要素をコピーしています。
Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 'コピー元の位置 Dim iPos As Integer = 6 'コピー先で3個の要素 Dim nLen As Integer = 3 Dim arr2(nLen - 1) As Integer 'コピー Array.Copy(arr, iPos, arr2, 0, nLen) 'コピー先の結果表示 Dim i As Integer = 0 For Each nEnt As Integer In arr2 Console.WriteLine("arr2({0}):{1}", i, nEnt) i += 1 Next End Sub実行結果がコンソールには以下様に表示されます。確かにコピーされている様です。
arr2(0):6 arr2(1):7 arr2(2):8
この処理を関数に以下の様にまとめてみました。'''
''' 配列切出し ''' ''' <param name="arr">切出し元の配列</param> ''' <param name="iPos">切出し位置</param> ''' <param name="nLen">要素数</param> '''切出した配列を返す Function SliceArray(ByVal arr() As Integer, ByVal iPos As Integer, ByVal nLen As Integer) As Integer() '指定位置が配列の大きさを超えた? If iPos >= arr.Length Then Return {} End If 'コピー最終位置 Dim iLast As Integer = iPos + nLen - 1 '指定位置からの指定個数が配列の大きさを超えた? If iLast >= arr.Length Then '個数の調整 nLen = arr.Length - iPos End If '返す配列の生成 Dim arrRet(nLen - 1) As Integer 'コピー Array.Copy(arr, iPos, arrRet, 0, nLen) '配列を返す Return arrRet End Function■配列を Enumerable.Skip、Enumerable.Take メソッドで処理
System.Linq.Enumerable の Skip メソッドを使うことで、配列内の一連の要素から指定位置から後ろのデータを全て取得します。 更に System.Linq.Enumerable の Take メソッドで先頭から指定個数の要素を切出します。
Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9} 'コピー '「arr」の先頭から6個の要素をスキップしてそれ以降を配列生成 Dim arr2() As Integer = System.Linq.Enumerable.Skip(arr, 6).ToArray '「arr2」の先頭から3個の要素のみで配列生成 arr2 = System.Linq.Enumerable.Take(arr2, 3).ToArray '以下の様に連結しても同様!! 'Dim arr2() As Integer = arr.Skip(6).Take(3).ToArray 'コピー先の結果表示 Dim i As Integer = 0 For Each nEnt As Integer In arr2 Console.WriteLine("arr2({0}):{1}", i, nEnt) i += 1 Next End Sub関連する記事
⇒配列の使い方について(Dim, Redim)
⇒配列の使い方の注意点について(コピー, Clone)
-
今回はクラスのコピーについて考えてみますが、以下の順で説明したいと思います。
- クラス変数のコピー
- クラスの実体コピー(Object.MemberwiseClone)
- クラスの実体コピー・参照型を含む(Object.MemberwiseClone)
- クラスの実体コピー・ディープコピー(Serializable)
■クラス変数のコピー
早速クラスのテストを以下のソースで行ってみます。
テスト用クラスとして Integer 型と String 型の2個のパブリック変数を持ち、 コンストラクタで値を設定するものとします。'テストクラス Class cTest 'パブリック変数 Public intData As Integer Public strData As String 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String) Me.intData = P_intData Me.strData = P_strData End Sub End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTest = New cTest(100, "AAAA") Dim cTst2 As cTest 'クラス変数のコピー cTst2 = cTst1 '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBB" 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}", cTst1.intData, cTst1.strData) Console.WriteLine("cTst2 : nData={0}, strData={1}", cTst2.intData, cTst2.strData) End Sub実行結果がコンソールには以下様に表示されます。
cTst1 : nData=200, strData=BBB cTst2 : nData=200, strData=BBB
クラス変数のコピーを行った後で、[cTst1]のデータを変更したつもりでも、[cTst2]のデータとして表示すると同じになってます。 これは、クラスの変数にはクラス生成されたデータ領域の参照が設定されるだけで、 クラス変数のコピーではその参照値(ポインタ)がコピーされるだけです。
結果、[cTst1]も[cTst2]も参照が指し示す実体は同じものとなり、 [cTst1]でデータにアクセスしても、[cTst2]でデータにアクセスしたことと同じになります。
どうしてもコピーしたい場合は、[cTst2]を新しいクラスで生成して、クラスの中身をそれぞれコピーすることはできます。 以下のソースでその例を示します。(しかし、これでは変数が多い場合は手間ですし、カッコよくありませんが...)
'テストクラス Class cTest 'パブリック変数 Public intData As Integer Public strData As String 'デフォルトのコンストラクタ Sub New() End Sub 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String) Me.intData = P_intData Me.strData = P_strData End Sub End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTest = New cTest(100, "AAAA") Dim cTst2 As cTest 'クラスのコピー cTst2 = New cTest cTst2.intData = cTst1.intData cTst2.strData = cTst1.strData '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBB" 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}", cTst1.intData, cTst1.strData) Console.WriteLine("cTst2 : nData={0}, strData={1}", cTst2.intData, cTst2.strData) End Sub実行結果がコンソールには以下様に表示されます。当然ですが、[cTst1]のデータと[cTst2]のデータは異なるものが表示されます。
cTst1 : nData=200, strData=BBB cTst2 : nData=100, strData=AAAA
本当はクラス自体が配列のコピーの Clone メソッドの様なものを持てればすっきりするのですが。
■クラスの実体コピー(Object.MemberwiseClone)
クラスにコピーメソッドを実装する為にソースを変更します。 先ずクラスにクローン作成をサポートするClone メソッドを定義し Object.MemberwiseClone を使って現在のクラスオブジェクトの簡易コピーを返してやります。
'テストクラス(Cloneメソッドを持つ) Class cTestWithClone Implements ICloneable 'パブリック変数 Public intData As Integer Public strData As String 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String) Me.intData = P_intData Me.strData = P_strData End Sub 'コピーメソッド Public Function Clone() As cTestWithClone Return MemberwiseClone() '現在の System.Object の簡易コピー作成 End Function End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTestWithClone = New cTestWithClone(100, "AAAA") Dim cTst2 As cTestWithClone 'クラスのクローンコピー cTst2 = cTst1.Clone '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBB" 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}", cTst1.intData, cTst1.strData) Console.WriteLine("cTst2 : nData={0}, strData={1}", cTst2.intData, cTst2.strData) End Sub実行結果がコンソールには以下様に表示されます。
cTst1 : nData=200, strData=BBB cTst2 : nData=100, strData=AAAA
これでほぼ目的は達成されるのですが、クラス内に参照型のデータが存在する場合、参照のみがコピーされるため注意が必要です。 (このあたりが、「簡易コピー」といわれる所以です。)
■クラスの実体コピー・参照型を含む場合(Object.MemberwiseClone)
クラスに参照型データとして Integer 型の配列を定義します。 例のコピーメソッドの実装では Object.MemberwiseClone を使って現在のクラスオブジェクトの簡易コピーを作成し、 作成できない参照型を個別にクローン生成してやります。
'テストクラス(Cloneメソッドを持つ) Class cTestWithClone 'パブリック変数 Public intData As Integer Public strData As String Public arr() As Integer 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String, ByVal P_arr() As Integer) Me.intData = P_intData Me.strData = P_strData Me.arr = P_arr End Sub 'コピーメソッド Public Function Clone() As cTestWithClone '先ず簡易コピー作成 Dim cTest As cTestWithClone = MemberwiseClone() '参照型データが設定済みならばクローン作製 If MyClass.arr IsNot Nothing Then cTest.arr = MyClass.arr.Clone End If 'コピーを返す Return cTest End Function End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTestWithClone = New cTestWithClone(100, "AAAA", {100, 200, 300}) Dim cTst2 As cTestWithClone 'クラスのコピー cTst2 = cTst1.Clone '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBBB" cTst1.arr(1) = 0 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}", cTst1.intData, cTst1.strData, cTst1.arr(0), cTst1.arr(1), cTst1.arr(2)) Console.WriteLine("cTst2 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}", cTst2.intData, cTst2.strData, cTst2.arr(0), cTst2.arr(1), cTst2.arr(2)) End Sub実行結果がコンソールには以下様に表示されます。
cTst1 : nData=200, strData=BBBB, arr(1)=100, arr(2)=0, arr(0)=300 cTst2 : nData=100, strData=AAAA, arr(1)=100, arr(2)=200, arr(0)=300
これで参照型データのコピーが出来ることが分かりました。 尚、参照型データが複数ある場合は、全てにおいて処理を繰り返す必要があります。
■クラスの実体コピー・ディープコピー(Serializable)
以下の記事にもありますが、クラスも1個のオブジェクトなのでクラスの実体をシリアル化して、別の実体にシリアル化を戻せば、 クラスのコピーができることになります。
⇒各種オブジェクトのコピーができるディープコピー(BinaryFormatter,MemoryStream,Serialize,Deserialize)
以下のソースに上記の記事内の関数を利用してクラスのコピーを行います。
クラスの宣言の先頭で <Serializable()> と記述し、シリアル化ができることを宣言しないと、関数でコピーができません。'テストクラス(シリアル化可能) <Serializable()> Class cTest 'パブリック変数 Public intData As Integer Public strData As String Public arr() As Integer 'コンストラクタ Sub New(ByVal P_intData As Integer, ByVal P_strData As String, ByVal P_arr() As Integer) Me.intData = P_intData Me.strData = P_strData Me.arr = P_arr End Sub End Class 'ボタンクリックでテストクラスのテスト Private Sub btnClassCopy_Click(sender As Object, e As EventArgs) Handles btnClassCopy.Click 'クラスの生成 Dim cTst1 As cTest = New cTest(100, "AAAA", {10, 20, 30}) Dim cTst2 As cTest 'クラスのコピー cTst2 = DeepCopy(cTst1) '[cTst1]のデータを変更 cTst1.intData = 200 cTst1.strData = "BBB" cTst1.arr(1) = 0 'クラスデータの中身を表示 Console.WriteLine("cTst1 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}", cTst1.intData, cTst1.strData, cTst1.arr(0), cTst1.arr(1), cTst1.arr(2)) Console.WriteLine("cTst2 : nData={0}, strData={1}, arr(1)={2}, arr(2)={3}, arr(0)={4}", cTst2.intData, cTst2.strData, cTst2.arr(0), cTst2.arr(1), cTst2.arr(2)) End Sub実行結果がコンソールには以下様に表示されます。
cTst1 : nData=200, strData=BBB, arr(1)=10, arr(2)=0, arr(0)=30 cTst2 : nData=100, strData=AAAA, arr(1)=10, arr(2)=20, arr(0)=30
関連する記事
⇒各種オブジェクトのコピーができるディープコピー(BinaryFormatter,MemoryStream,Serialize,Deserialize)
-
前回の以下の記事は配列の使い方の基本について記しましたが、 今回は配列を使う場合の注意点をまとめてみました。
⇒配列の使い方について(Dim, Redim)
それでは以下の内容で説明したいと思います。■配列変数のコピー
以下のソースを見て下さい。 最初に配列「arr1」「arr2」を宣言していますが、「arr1」のみ初期化も行っています。
その後、「arr2」に「arr1」を代入しています。 (この代入で「arr2」に「arr1」の内容全てがコピーされた様に思いますが、実際にそうでしょうか。)
「arr1」の2番目の要素クリア後、「arr1」及び「arr2」の内容を全てコンソールに表示させてどうなったかを見てみます。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言 Dim arr1() As Integer = {100, 200, 300} 'こちらは配列初期化有り Dim arr2() As Integer '配列宣言のみ '配列変数のコピー arr2 = arr1 '「arr1」の2番目の要素クリア arr1(1) = 0 '「arr1」の内容表示 For i As Integer = 0 To UBound(arr1) Console.WriteLine("arr1[{0}]:{1}", i, arr1(i)) Next '「arr2」の内容表示 For i As Integer = 0 To UBound(arr2) Console.WriteLine("arr2[{0}]:{1}", i, arr2(i)) Next End Sub実行結果がコンソールには以下様に表示されます。
arr1[0]:100 arr1[1]:0 arr1[2]:300 arr2[0]:100 arr2[1]:0 arr2[2]:300
予想に反して「arr1」及び「arr2」共に同じ値が表示されました。
どうしてこうなるのかと言いますと、「配列変数のコピー」では配列の中身では無く参照をコピーすることになるからです。 参照とは配列の実体を指し示すポインタと言った方がいいかもしれません。
実は変数「arr1」も配列の実体の参照でしかないということです。 {100, 200, 300} と宣言された配列の実体はどこかにあり、その実体を指し示すポインタを変数「arr1」が保持しています。
実体をコピーしたい場合には配列の Clone メソッドを使います。以下にその様子を示します。
■配列変数の実体コピー(Clone)
上記のソースでは配列変数コピーで単に変数同士のコピーを行いましたが、 以下では配列の Clone メソッドの結果を代入しています。
Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言 Dim arr1() As Integer = {100, 200, 300} 'こちらは配列初期化有り Dim arr2() As Integer '配列宣言のみ '配列変数の実体コピー arr2 = arr1.Clone '「arr1」の2番目の要素クリア arr1(1) = 0 '「arr1」の内容表示 For i As Integer = 0 To UBound(arr1) Console.WriteLine("arr1[{0}]:{1}", i, arr1(i)) Next '「arr2」の内容表示 For i As Integer = 0 To UBound(arr2) Console.WriteLine("arr2[{0}]:{1}", i, arr2(i)) Next End Sub実行結果がコンソールには以下様に表示されます。
arr1[0]:100 arr1[1]:0 arr1[2]:300 arr2[0]:100 arr2[1]:200 arr2[2]:300
結果を見れば指標「1」の要素が「arr1」及び「arr2」が異なる値であることが分かります。
Clone メソッドは配列の実体を別に作成し、全ての要素をコピーしその参照を返します。 (Clone 直後は要素の内容は全て同じです。) そのため、「arr1」及び「arr2」は実体が別々の配列となります。
■配列の実体コピー・ディープコピー(Serializable)
以下の記事にもありますが、配列も1個のオブジェクトなので配列の実体をシリアル化して、別の実体にシリアル化を戻せば、 配列のコピーができることになります。
⇒各種オブジェクトのコピーができるディープコピー(BinaryFormatter,MemoryStream,Serialize,Deserialize)
以下のソースに上記の記事内の関数を利用して配列のコピーを行います。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言 Dim arr1() As Integer = {100, 200, 300} 'こちらは配列初期化有り Dim arr2() As Integer '配列宣言のみ '配列の実体コピー・ディープコピー arr2 = DeepCopy(arr1) '「arr1」の2番目の要素クリア arr1(1) = 0 '「arr1」の内容表示 For i As Integer = 0 To UBound(arr1) Console.WriteLine("arr1[{0}]:{1}", i, arr1(i)) Next '「arr2」の内容表示 For i As Integer = 0 To UBound(arr2) Console.WriteLine("arr2[{0}]:{1}", i, arr2(i)) Next End Sub実行結果がコンソールには以下様に表示されます。
arr1[0]:100 arr1[1]:0 arr1[2]:300 arr2[0]:100 arr2[1]:200 arr2[2]:300
関連する記事
⇒配列の使い方について(Dim, Redim)
⇒配列の範囲指定によるコピー(Array.Copy, Skip, Take)
-
配列は似た性質のデータをまとめて取り扱えるようにするデータ形式です。 配列の機能は、その昔の言語である FORTRAN , C言語 (私も使った言語ですが)等でも持っています。 ここまで配列の機能が残っているということは、それなりに使い勝手が良いのではないかと思います。 そこで今回は、配列の使い方について順を追って説明したいと思います。
■簡単な配列の宣言と使い方
以下のソースを見て下さい。 処理の前半は、配列を利用しない場合の合計処理を示しています。 配列が無い場合は、値を別々の変数に宣言して、それぞれを参照し合計を求めています。 配列を利用しないと非常に煩雑になってしまします。
処理の後半で配列を利用する場合を示しています。 配列の各要素に値を設定する部分は、ベタに記述していますが、合計を求めるところで 配列の処理らしさが現れています。 (この処理などは初歩の初歩なので特に難しくないと思います。)Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '===配列を利用しない場合の合計処理=== '各変数の宣言 Dim nVal1 As Integer Dim nVal2 As Integer Dim nVal3 As Integer Dim nVal4 As Integer Dim nVal5 As Integer '値の設定 nVal1 = 1 nVal2 = 10 nVal3 = 100 nVal4 = 1000 nVal5 = 10000 '値の合計 Dim nSum As Integer nSum = nVal1 + nVal2 + nVal3 + nVal4 + nVal5 Console.WriteLine("配列を利用しない場合の合計処理:{0}", nSum) '===配列を利用=== '配列宣言 Dim arr(5) As Integer '要素に値設定 arr(1) = 1 arr(2) = 10 arr(3) = 100 arr(4) = 1000 arr(5) = 10000 '指標宣言 Dim i As Integer '合計計算 nSum = 0 For i = 1 To 5 nSum = nSum + arr(i) Next Console.WriteLine("配列を利用した場合の合計処理:{0}", nSum) End Sub実行結果がコンソールには以下様に表示されます。(当然合計値は同じ結果)
配列を利用しない場合の合計処理:11111 配列を利用した場合の合計処理:11111
■配列宣言の落とし穴
上記のソースでは配列宣言で Dim arr(5) As Integer と宣言しましたが、 領域としては指標が「1」~「5」まで確保されたかの様に感じますが、実は指標「0」の要素が存在します。
そこで上記のソースを以下の様に変更し実行してみます。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '===配列を利用=== '配列宣言 Dim arr(5) As Integer '要素に値設定 arr(1) = 1 arr(2) = 10 arr(3) = 100 arr(4) = 1000 arr(5) = 10000 arr(0) = 10000 '指標「0」の要素 '指標宣言 Dim i As Integer '合計計算 nSum = 0 For i = 0 To 5 nSum = nSum + arr(i) Next Console.WriteLine("配列を利用した場合の合計処理:{0}", nSum) End Sub配列を利用した場合の合計処理:21111結果を見れば指標「0」の要素が存在することが分かります。
Dim arr(5) で5個の領域を宣言したつもりが指標「0」の要素が存在するため、 全体では6個の領域を宣言したことになります。
配列の開始が指標「0」と考えるのか、それとも指標「1」からとするのかで、プログラムの動作や組み方が変わってきます。 複数の人で組む場合にはなおさらで、どちらにするのかを決めておく必要があります。
配列の開始が指標「0」を使うのであれば、必要個数の1個少な目で配列宣言するのか、 それとも指標「1」ならば必要個数で配列宣言し、指標「0」の要素は未使用とするか、と言ったところです。
■配列の初期化
配列の宣言で一緒に初期化ができます。 配列の要素数を記述せずに、宣言のデータ型の後に、=(イコール) で繋ぎ {}(中括弧) で要素を ,(カンマ) で区切って記述します。
上記のソースを配列の初期化を行う様に変更します。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {10000, 10000, 1000, 100, 10, 1} '指標宣言 Dim i As Integer '合計計算 Dim nSum As Integer nSum = 0 For i = 0 To 5 nSum = nSum + arr(i) Next Console.WriteLine("配列宣言(初期化)を利用した場合の合計処理:{0}", nSum) End Sub
このソース合計計算の For ループで「5」を直値(リテラル)で記述しています。 この場合は「5」であることが明らかなので問題は無いのですが、 配列の最終の指標が何か分からない時には UBound 等を用います。 以下に項目を新しくして説明します。
■配列の最終指標はUBound
UBound 関数は配列内の指定された次元における最も大きいインデックスを返します。
Public Function UBound(Array As System.Array, Optional Rank As Integer = 1) As Integer 引数: ・Array:任意のデータ型の配列です。 ある次元で最も大きいインデックスを探す対象となる配列です。 ・Rank :省略可能です。 Integer. 最も大きいインデックスが返される次元です。 1番目の次元の場合は「1」、2番目の次元の場合は「2」という形で指定します。 Rank を省略した場合、「1」が使用されます。 戻り値:Integer型 ・指定した次元に設定できるインデックスの最大値です。 ・Array に要素が 1 つしかない場合、UBound は 0 を返します。 ・Array に要素が存在しない場合 (長さ 0 の文字列の場合など)、UBound は -1 を返します。
以下の例では For ループの最終指標にこの UBound 関数を使っています。
尚、配列のプロパティのには現在の配列の大きさ(長さ)を返してくれる Length がありますのでそれを使っても同様です。 但し、 Length は個数なので「-1」する必要があります。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {10000, 10000, 1000, 100, 10, 1} '指標宣言 Dim i As Integer '合計計算 Dim nSum As Integer = 0 '[Ubound]を使った最終指標 For i = 0 To UBound(arr) nSum = nSum + arr(i) Next Console.WriteLine("配列宣言(初期化)を利用した場合の合計処理:{0}", nSum) nSum = 0 '配列の大きさを返すプロパティ[Length]を使った最終指標 For i = 0 To arr.Length - 1 nSum = nSum + arr(i) Next Console.WriteLine("配列宣言(初期化)を利用した場合の合計処理:{0}", nSum) End Sub■配列のサイズ変更(ReDim)
配列のサイズを変更するために ReDim 命令を使います。
以下のソースでは ReDim 命令で指標が「2」までのサイズに縮めています。 (合計の結果は「21000」を想定していますが...)Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化:要素6個) Dim arr() As Integer = {10000, 10000, 1000, 100, 10, 1} '配列サイズ変更 ReDim arr(2) '合計計算 Dim nSum As Integer = 0 '[Ubound]を使った最終指標 For i As Integer = 0 To UBound(arr) nSum = nSum + arr(i) Next Console.WriteLine("配列をReDimでサイズ変更した場合の合計処理:{0}", nSum) End Subしかし結果は「0」となってしまいます。
配列をReDimでサイズ変更した場合の合計処理:0これは ReDim 命令はサイズを変更した後で全ての要素をその配列のデータ型で初期化してしまうからです。 既に存在した要素を引き継ぐ場合には ReDim 命令で Preserve を指定します。
Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化:要素6個) Dim arr() As Integer = {10000, 10000, 1000, 100, 10, 1} '配列サイズ変更(Preserve指定) ReDim Preserve arr(2) '合計計算 Dim nSum As Integer = 0 '[Ubound]を使った最終指標 For i As Integer = 0 To UBound(arr) nSum = nSum + arr(i) Next Console.WriteLine("配列をReDimでサイズ変更した場合の合計処理:{0}", nSum) End Sub結果は想定通り「21000」となりました。
配列をReDimでサイズ変更した場合の合計処理:21000
■配列のサイズ未指定での宣言
配列のサイズを指定せずに配列を宣言することができます。 この場合、配列は空の状態で存在し ReDim 命令でサイズを増やしてやらないと使用できません。 (空の状態の状態は Is Nothing で判定できます)
それでは、以下にInteger型配列をサイズ無しで宣言し、配列のサイズを順次拡張して中身が「0」~「9」となる 配列を作成する例を示します。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言 Dim arr() As Integer ' = Nothing はあった方がいいと思います '「0」~「9」の処理 For i As Integer = 0 To 9 If arr Is Nothing Then '最初は指標「0」 ReDim arr(0) arr(0) = i Else '次の指標 Dim idx As Integer = UBound(arr) + 1 'サイズ拡張 ReDim Preserve arr(idx) '要素設定 arr(idx) = i End If Next '配列の要素を文字列として連結 Dim str As String = "" For i As Integer = 0 To UBound(arr) If str <> "" Then str &= "," str &= arr(i) Next Console.WriteLine("配列:{0}", str) End Sub配列:0,1,2,3,4,5,6,7,8,9
■配列のループ処理は For Each
上記に配列のループ処理の最終指標として UBound Array.Length を使いましたが、 指標に関係なく配列の要素を順次取得する方法が For Each … Next 処理です。 以下の実際のソースで直感的に分かると思います。
For Each の後ろに配列の要素と同じデータ型の変数を記述し In の後ろに対象となる配列を記述します。
Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {10000, 10000, 1000, 100, 10, 1} '要素宣言 Dim nEnt As Integer '合計計算 Dim nSum As Integer = 0 For Each nEnt In arr nSum += nEnt Next Console.WriteLine("For Each ... Nexを利用した場合の合計処理:{0}", nSum) End SubFor Each ... Nexを利用した場合の合計処理:21111
尚、要素用の変数を別で宣言しなくても以下の様にしても同様な結果となります。Private Sub btnArray_Click(sender As Object, e As EventArgs) Handles btnArray.Click '配列宣言(初期化) Dim arr() As Integer = {10000, 10000, 1000, 100, 10, 1} '合計計算 Dim nSum As Integer = 0 For Each nEnt As Integer In arr nSum += nEnt Next Console.WriteLine("For Each ... Nexを利用した場合の合計処理:{0}", nSum) End Sub関連する記事
⇒配列の使い方の注意点について(コピー, Clone)
⇒配列の範囲指定によるコピー(Array.Copy, Skip, Take)