[[20130808161622]] 『固定長テキスト処理について』(フェンダー) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『固定長テキスト処理について』(フェンダー)

 お世話になります。
[[20130730084752]]で質問した話の続きになります。
 COBOL言語で作成されたシステムという事で
 下記のような1レコードに対し1件の
 固定長テキストデータという事でほぼ決まりのようです。
 現在は1バイトEBCDICと
    2バイトKEISですが
 今後は1byte2byteともにAsciiになるということになります。

 今回の下記のテスト例で言いますと
 レコード長 30byte
 1項目目     5byte
 2項目目    10byte
 3項目目    15byteになるのですが

 001  あああ    おおおお       
 002  いい      いいいい       
 003  うううううかかかかかかか

 固定長テキストデータを
 項目ごとにカンマで区切りCSVデータのように
 ファイルを生成することを
 まず考えたいのですが
 上記のようなイメージのサンプルデータがあるサイトはありますでしょうか?
 もしくはアドバイスの方
 よろしくお願いいたします。

(フェンダー)


 質問するのも良いと思いますが、「固定長 CSV 変換」などのキーワードで検索は
 してみたでしょうか。
 世にあふれた作業だと思いますので、まずは探してみてはと思います。

 蛇足ですが、2byte の ASCII 文字というものは存在しません。
 (Mook)

(Mook)さん

もちろん色々探しました。
専用コンバーターだったりなかなか参考になるものがなかったので・・・
もう少し探してみます。

(フェンダー)


 例えば
http://www.asahi-net.or.jp/~ef2o-inue/vba_o/sub05_110_055.html
 などは参考にならなかったのでしょうか。

 フリーウェアでも
http://download.goo.ne.jp/software/contents/soft/winnt/util/se432913.html
 のようなものがあるようですが、使用できないでしょうか。
 (Mook)

(Mook)さん

質問する前にチェックしてたサイトですが
サンプルコードもありますし、よく確認してなかったです。

フリーウェアは、仕様によって変わると思いますので
対応出来る場合もあるかと思いますが厳しいかと思われます。
どうもありがとうございました。

(フェンダー)


 以前にも記述しましたが、固定長のファイルなら、VBAの標準ファイルI/Oにも
 専用モードがあります。

 AKBの総選挙結果を例にとると・・・、

 3バイト(順位) 8バイト(氏名)8バイト(得票数)の計19バイトが1レコードのファイルを考えます

 001指原莉乃 150,570
 002大島優子 136,503
 003渡辺麻友 101,210

 こんなデータです。

 新規ブックの標準モジュールに

 Option Explicit
 Type rec
   rnk   As String * 3
   nm As String * 8
   vl As String * 8
 End Type
 Type brec
  rnk(1 To 3) As Byte
  nm(1 To 8) As Byte
  vl(1 To 8) As Byte
 End Type
 '=========================================
 Sub 書き込み()
    Dim fno As Long
    Dim dat As rec
    On Error Resume Next
    Kill ThisWorkbook.Path & "\test.dat"
    On Error GoTo 0
    fno = FreeFile
    Open ThisWorkbook.Path & "\test.dat" For Random As #fno Len = 19
    With dat
       .rnk = Format(1, "000")
       .nm = "指原莉乃"
       .vl = Space(8)
       RSet .vl = Format(150570, "#,##")
    End With
    Put #fno, , dat
    With dat
       .rnk = Format(2, "000")
       .nm = "大島優子"
       .vl = Space(8)
       RSet .vl = Format(136503, "#,##")
    End With
    Put #fno, , dat
    With dat
       .rnk = Format(3, "000")
       .nm = "渡辺麻友"
       .vl = Space(8)
       RSet .vl = Format(101210, "#,##")
    End With
    Put #fno, , dat
    Close #fno
 End Sub
 '================================================
 Sub 読み込み()
    Dim fno As Long
    Dim dat2 As brec
    Dim g0 As Long
    fno = FreeFile
    Open ThisWorkbook.Path & "\test.dat" For Random As #fno Len = 19
    For g0 = 1 To LOF(fno) / 19
        Get #fno, , dat2
        With dat2
           Debug.Print StrConv(.rnk(), vbUnicode) & " : " & StrConv(.nm(), vbUnicode) & " : " & StrConv(.vl(), vbUnicode)
        End With
    Next
    Close #fno
 End Sub

*ブックを一度適当な個所に保存後に

 書き込みを実行してください。これでブックと同じフォルダ上に固定長ファイルが作成されます。

 次に問題の読み込みは、読み込みを実行してください。

 正しくデータが取得できると思います。

 ポイントは、
 Typeステートメントでユーザー定義型の変数を定義すること

 読み込みは Getステートメントを使用する

 でしょうか!!

 Cobolからの出力ファイル形式が全部文字列型のデータなら、よいですが、
 Cobol言語にも VBAでいう Single型やDouble型などのバイナリエディタなどでも何のデータなのか
 分からないような変数はあります。

 汎用的に利用できる出力ファイルだとすれば、あまりこういう変数は使いませんが、
 逆にこれらの変数を使うと簡単に中の内容は想像できませんから、そういう目的もあるなら、
 可能性としては、0ではありませんね!!

 そのような場合でも上記の手法であれば、Typeステートメントの定義次第で
 簡単にデータ取得はできます。

 検討してみてください

 ichinose

 


 続きです。
 >固定長テキストデータ
 これが行区切り記号あり(1レコードの末尾に&h0D0Aがある)固定長データの場合。
 前回データを以下のように変更

 AKBの総選挙結果を例にとると・・・、
 3バイト(順位) 8バイト(氏名)6バイト(得票数)の
 計17バイトが1レコード(実際には&h0D0Aを含めて19バイト)のファイルを考えます

 001指原莉乃150570
 002大島優子136503
 003渡辺麻友101210

 説明の都合上、数字の区切りカンマを取りました。

 こんなデータです。

 新規ブックの標準モジュール(Module1)に

 '============================================================
 Option Explicit
 Type rec
   rnk   As String * 3
   nm As String * 8
   vl As String * 6
   clf As String * 2
 End Type
 Sub 書き込み2()
    Dim fno As Long
    Dim dat As rec
    On Error Resume Next
    Kill ThisWorkbook.path & "\test2.txt"
    On Error GoTo 0
    fno = FreeFile
    Open ThisWorkbook.path & "\test2.txt" For Random As #fno Len = 19
    With dat
       .rnk = Format(1, "000")
       .nm = "指原莉乃"
       .vl = Space(6)
       RSet .vl = CStr(150570)
       .clf = vbCrLf
    End With
    Put #fno, , dat
    With dat
       .rnk = Format(2, "000")
       .nm = "大島優子"
       .vl = Space(6)
       RSet .vl = CStr(136503)

    End With
    Put #fno, , dat
    With dat
       .rnk = Format(3, "000")
       .nm = "渡辺麻友"
       .vl = Space(6)
       RSet .vl = CStr(101210)
    End With
    Put #fno, , dat
    Close #fno
 End Sub

 別の標準モジュール(Module2)にADO I/O プロシジャー群(テキストファイル用)

 '==============================================================
 Option Explicit
 '======================================================
 Private cn As Object
 '=============================================================
 Function open_ado_text(path As String) As Long 'adoでテキストにアクセス
    On Error Resume Next
    Dim link_opt As String
    Set cn = CreateObject("adodb.connection")
    link_opt = "Driver={Microsoft Text Driver (*.txt; *.csv)};" & _
                 "DBQ=" & path & ";" & "ReadOnly=0"
    cn.Open link_opt
    open_ado_text = Err.Number
    On Error GoTo 0
 End Function
 '=============================================================
 Sub close_ado()    'クローズ
    On Error Resume Next
    cn.Close
    Set cn = Nothing
    On Error GoTo 0
 End Sub
 '=============================================================
 Function exec_sql(sql_str, Optional rs As Variant) As Long  'Sqlの実行
    On Error Resume Next
    If IsMissing(rs) Then
       cn.Execute sql_str
    Else
       Set rs = cn.Execute(sql_str)
    End If
    exec_sql = Err.Number
    If Err.Number <> 0 Then MsgBox Err.Description
    On Error GoTo 0
 End Function
 '==========================================================================
 Function mk_schema_ini(path As String, dat() As String) As Long
 'schema.iniの作成
    On Error GoTo err_mk_schema_ini
    Dim fno As Long
    Dim didx As Long
    mk_schema_ini = 0
    fno = FreeFile()
    Open path & "\schema.ini" For Output As #fno
    For didx = LBound(dat()) To UBound(dat())
       Print #fno, dat(didx)
       Next
    Close #fno
 ret_mk_schema_ini:
    On Error GoTo 0
    Exit Function
 err_mk_schema_ini:
    MsgBox Err.Description
    mk_schema_ini = Err.Number
    Resume ret_mk_schema_ini
 End Function
 '=============================================================
 Function del_schema_ini(path As String) 'schema_iniの削除
    On Error Resume Next
    Kill path & "\schema.ini"
    On Error GoTo 0
 End Function

 別の標準モジュール(Module3)に固定長テキストファイル読み込みプロシジャー

 '========================================================================
 Option Explicit
 '==============================================================
 Sub 読み込み2()
    Dim ret As Long
    Dim dat(1 To 8) As String
    Dim rs As Object
    Dim ans As Variant
    dat(1) = "[test2.txt]"
    dat(2) = "ColNameHeader = False"
    dat(3) = "Format = FixedLength"
    dat(4) = "MaxScanRows = 0"
    dat(5) = "CharacterSet = OEM"
    dat(6) = "Col1=rnk char width 3"
    dat(7) = "Col2=nm Char Width 8"
    dat(8) = "Col3=vl Char Width 6"
    Call mk_schema_ini(ThisWorkbook.path, dat())
    ret = open_ado_text(ThisWorkbook.path)
    If ret = 0 Then
       ret = exec_sql("select * from test2.txt;", rs)
       If ret = 0 Then
          Do Until rs.EOF
             MsgBox "順位 : " & rs!rnk & " 氏名 : " & rs!nm & " 得票数 : " & rs!vl
             rs.movenext
          Loop
          rs.Close
       Else
          MsgBox Error(ret)
       End If
       close_ado
       End If
    Call del_schema_ini(ThisWorkbook.path)
    Erase dat()
    Set rs = Nothing
 End Sub

*ブックを一度適当な個所に保存後に

 書き込み2 を実行してください。これでブックと同じフォルダ上に固定長テキストファイルが作成されます(行区切りあり)。

 次に問題の読み込みは、読み込み2 を実行してください。
 正しくデータが取得できると思います。

 ポイントは、
 Schema.iniというファイルに固定長ファイルtest2.txtの情報を書き込む
 SQLでデータを取り出す
 adoのRecordSetの扱い方でしょうか!!

 >固定長テキストデータを
 >項目ごとにカンマで区切りCSVデータ

 後は、取り出したデータをもとにこれをCSVに変換すればよいです。

 実は、CSVファイルは、作成するCSVファイルの情報をSchema.iniファイルに追加し、
 SQLをそれように変更すれば、簡単に作ってくれます(Select into を使う)が、
 それを調べるか、Textstream等を使ってカンマ区切りに編集するのもよいでしょうね

 以上です。

 ichinose


色々ありがとうございます。
ユーザー定義型の変数を定義して
バイナリ入出力のように
getステートメントを使用して、検証し
現在実行結果が思うようにいかない感じでした。
続いて、記述して頂いたコードと
アドバイスを参考に
色々調べながら試してみます。
時間はありますので、アドバイス頂けて
助かります。
(フェンダー)


 >ユーザー定義型の変数を定義して 
 >バイナリ入出力のように 
 >getステートメントを使用して、検証し 
 >現在実行結果が思うようにいかない感じでした。

 うまくいかない現象を具体的に説明してください。
 私の方で再度、投稿したコードを試してみましたが、正常にデータが取得できています。

 >Debug.Print StrConv(.rnk(), vbUnicode) & " : " & StrConv(.nm(), vbUnicode) & " : " & StrConv(.vl(), vbUnicode)

 これが問題で結果が表示されない ということなら、
 msgbox StrConv(.rnk(), vbUnicode) & " : " & StrConv(.nm(), vbUnicode) & " : " & StrConv(.vl(), vbUnicode)

 これに変更してください。そうでなかったら、具体的な説明をお願いします。

 ichinose


EBCDICとKEISですか? 前のご質問ではEBCDIKとKEISでしたし、普通はEBCDIKとKEISの
組合せで使用し、EBCDICとKEISにはしないはずですが。 正確な情報を出さないと、
欲しい回答は得られませんよ。

EBCDICとEBCDIKの違いをご存知ないようならば、日立のページにある情報を
確認することです。固定長ファイルのアクセスならばMookさんが教えてくれている
ページが参考になりますし、そこまでしなくとも30byte全てByte型配列1つで
読むだけでも十分でしょう。(改行コードが無いようですが、本当に?)

http://www.hitachi.co.jp/Prod/comp/soft1/manual/pc/d3J3820/ISUS0268.HTM
http://www.hitachi.co.jp/Prod/comp/soft1/manual/ws/c3D5820/CLNT0235.HTM

面倒なのはコード変換部分です。といっても、前回のご質問では変換は他者が
行なっているとの事なので、何も悩む部分は無いように思えます。
(???)


もう少し詳しく指摘すると、5byteのデータはKI/KOを含む余地が殆ど無いので、ここはEBCDIKと思われます。

10byteは、全てKEISコードでしょうか。要確認。

全て漢字の場合、データ長は偶数のはずなので、15byteは全角半角混在と思います。
データ中にKI/KOコードは含みますか? KI前提でEBCDIKとKEISの混在かと思いますが、要確認。

なお、データ中に&H0Aが含まれる場合、KEISではKI/KOの先頭1byteであり、改行ではない事に注意。
(???)


失礼いたしました。
EBCDIKとKEISになります。

>(改行コードが無いようですが、本当に?)

データは区切りのないファイルとなります。

>面倒なのはコード変換部分です。といっても、前回のご質問では変換は他者が

 行なっているとの事なので、何も悩む部分は無いように思えます。

申し訳ございません。
こちらがよく分からないのですが
もう一度整理させて頂きます。
現在EBCDIKとKEISのデータに関しては
対応出来ております。

特に悩んでないのですが・・・

(フェンダー)


 前スレも見て確認です。
 現在行っている処理が、システム変更により、
 input:
  >現在は1バイトEBCDICと
  >   2バイトKEISですが
  >今後は1byte2byteともにAsciiになる 
 output:
  記載はないので仕様がわかりませんが、変更なし
 ということですね。
 (cai)

>うまくいかない現象を具体的に説明してください。

ichinoseさんのコードは参考でチェックさせて頂きましたが
アドバイスを元に
バイナリの入出力にそのまま変数を変数を定義している状態ですが
Textstreamを使用して区切りをつける方法等
調べている感じです。
下記のコードがVBAになります。

 ファイルの中身

 001指原莉乃150570
 002大島優子136503
 003渡辺麻友101210

現在

model1

Sub Main()

Dim InFileName As String
Dim OutFileName As String

Dim InFN As Long '入力ファイルハンドル番号
Dim OutFN As Long '出力ファイルハンドル番号
Dim InFileLen As Long '入力ファイルのサイズ
Dim InPOS As Long '入力ファイル位置
Dim OutPOS As Long '出力ファイル位置

InFileName = "\SAMPLE.dat"
OutFileName = "\outSAMPLE.dat"

InFN = FreeFile
Open InFileName For Binary As #InFN
OutFN = FreeFile
Open OutFileName For Binary As #OutFN

InPOS = 1
OutPOS = 1
InFileLen = LOF(InFN)

Do While (InPOS < InFileLen)

  '1************************
   Get #InFN, InPOS, フィールド1

  Put #OutFN, OutPOS, フィールド1
  InPOS = InPOS + 3
  OutPOS = OutPOS + 3
  '1************************

  '2************************
  Get #InFN, InPOS, フィールド2

  Put #OutFN, OutPOS, フィールド2
  InPOS = InPOS + 8
  OutPOS = OutPOS + 8
  '************************

 '3************************
  Get #InFN, InPOS, フィールド3

  Put #OutFN, OutPOS, フィールド3
  InPOS = InPOS + 6
  OutPOS = OutPOS + 6
 '************************

  ipos = ipos + 1
Loop

Close #InFN
Close #OutFN

End Sub

model2

Public フィールド1(1 To 3) As Byte '1
Public フィールド2(1 To 8) As Byte '2
Public フィールド3(1 To 6) As Byte '3

上記コードでおかしな箇所をご指摘ください。

それとこちらは
言語が違くて申し訳ございませんが
ichinoseさんのアドバイスから作成してますので
記載します。

カンマを含んだテキストデータを
入出力する際
byte[] から String をにし
区切りはバーティカルバーに設定。

すると
下記のソースでの
結果が現在こんな結果になります。

出力前

 001指原莉乃15,570
 002大島優子13,503
 003渡辺麻友10,210

出力後
001|指原莉乃|15,570
002|大島優子|13,503
003|渡辺麻友|10,210

String arrayline = recordlength;

try {
  byte[] bytes = arrayline.getBytes("MS932");
  byte[] first = new byte[3]; //連番
  byte[] second = new byte[8]; //氏名
  byte[] third = new byte[6]; //金額

	int findex = 0;
	int sindex = 0;
	int tindex = 0;
	for (int i = 0; i< bytes.length;i++){
	if(i< 3){
			first[findex] = bytes[i];
				findex ++ ;
					}
			else if(i< 11){
				second[sindex] = bytes[i];
				sindex ++ ;
			}
			else if(i< 17){
				third[tindex] = bytes[i];
						tindex ++ ;
					}
				}

	System.out.print(new String(first,"MS932")+ "|");
	System.out.print(new String(second,"MS932")+ "|");
	System.out.println(new String(third,"MS932"));

	} catch (UnsupportedEncodingException e) {
			e.printStackTrace();}
			}

この方法で問題ないのか?他の仕様の際対応出来るのか
こちらが懸念されるとこだと思います。

(フェンダー)


つまりは、既にコード変換は終わっている。しかしカンマも改行もないので、
要所にコード挿入してcsv形式として開けるようにしたい、ということ?
(ならばEBCDIKだのKEISだの、余計なコード情報は書かないで欲しい)

標準モジュールで以下を実行。

 Type aaa
    b1(4) As Byte
    b2(9) As Byte
    b3(14) As Byte
 End Type

 Sub test()
    Dim F1 As Integer
    Dim F2 As Integer
    Dim Rec As aaa

    F1 = FreeFile
    Open "\SAMPLE.dat" For Binary As #F1
    F2 = FreeFile
    Open "\SAMPLE.csv" For Binary As #F2
    While EOF(F1) = False
        Get #F1, , Rec
        Put #F2, , Rec.b1
        Put #F2, , ","
        Put #F2, , Rec.b2
        Put #F2, , ","
        Put #F2, , Rec.b3
        Put #F2, , vbCrLf
    Wend
    Close #F2
    Close #F1
 End Sub

.NETのコードが書けるなら、何も遅いExcelVBAなぞ使わなくても良さそうな…。
(???)


 因みに、文字コード1byte
 2byteともにshift-jisとお伝えしていましたが
 仕様によってはバイナリデータも混在しているとの事です。
 どのようなバイナリデータが含まれるのかは
 実際テストデータを授受してからの話に
 なると思いますが…
 現在コード変換が出来ている入出力のソースは
 保留になりそうなので
 バイナリを操作するとなると
 VBAの方がいいかなと。

(フェンダー)


 Javaは、知らないのでなんとも言えませんが、
 CSVファイル用に取得したランダムファイルから取得したデータの間に「,」を入れて、後は繋げれば
 良いのですから、方法はいくらでもあると思います。
 色々考えてみてください。 私は、ここは、フェンダーさんがご自分で考える範囲だと
 最初から決めていましたので・・・。できると思いますよ!!

 それより・・・、
 >仕様によってはバイナリデータも混在しているとの事です。

 あくまでも想像ですが、これの扱いが今までとは違うかもしれません。

 バイナリデータの値と内部表現をいくつも事例から調べるなりしてみてください

 そのためには、ファイルの内部表現が簡単に見ることができるバイナリーエディタが必要です。
 フリーでDLできるもので構いませんから、ないのなら準備はしておいてください。

 ichinose


ご連絡ありがとうございます。
JAVAは基本的に
件数が多いデータを早く処理したい為に
使っているのですが
VBAもJAVAも同じですので
こちらで教えて頂いた内容を
JAVAで書いたりもしてます。
(JAVAの方が便利なメソッドが用意されていて
内容によっては初心者には便利)

まだ時間はありますので、色々試してみますが
出来てるものをそのまま使用するとなると
エラー時に対応出来なくなるもしくは
想像外の事がおきたりすることもあると思います。
本来作成したいものは
分かりやすいコードで、色々な仕様と
色々なエラーにすぐに対応したいので
出来れば、対応出来てるプログラムと併用できる
ものが、効率が良く安心かなと思ってます。

バイナリ型に関しては色々と調べて事前準備をしますが
こちらはテストデータを授受してからになりそうです。

バイナリエディタに関してはStirlingを使用してます。
byte数(レコード長)は必ず把握しなければ
しっかりしたデータが生成できないので
バイナリ型やパック型等、数値変換する際には
必ずバイト数をチェックしてます。

(フェンダー)


 (???)さん。

 色々コメントありがとうございます。
 こちらのコードも参考にさせて頂きます。

(フェンダー)


下記のコードで解決致しました。
まずデータは

001指原莉乃150570
002大島優子136503
003渡辺麻友101210

変数は1レコードの末尾に&h0D0Aがある場合を
考慮しh0D0A(1 To 2) As Byteを追加し
各フィールドごとのカンマを付加させる分
読み込むbyte数に対して
領域を1byteずつ確保しました。

'1フィールド*******************

  Get #InFN, InPOS, 連番

  Put #OutFN, OutPOS, 連番

  InPOS = InPOS + 3
   Put #OutFN, , ","
  OutPOS = OutPOS + 4
'************************

'2フィールド******************

  Get #InFN, InPOS, 氏名

  Put #OutFN, OutPOS, 氏名

  InPOS = InPOS + 8
  OutPOS = OutPOS + 9
  Put #OutFN, , ","

'************************

'3フィールド******************

  Get #InFN, InPOS, 投票数

  Put #OutFN, OutPOS, 投票数

  InPOS = InPOS + 6
  OutPOS = OutPOS + 6
'************************

'1レコードの末尾&h0D0A*************

  Get #InFN, InPOS, h0D0A

  Put #OutFN, OutPOS, h0D0A

  InPOS = InPOS + 2
  OutPOS = OutPOS + 2
'************************

Public  連番(1 To 3) As Byte
Public  氏名(1 To 8) As Byte
Public 投票数(1 To 6) As Byte
Public h0D0A(1 To 2) As Byte '1レコードの末尾に&h0D0Aがある場合

これにより結果が

001,指原莉乃,150570
002,大島優子,136503
003,渡辺麻友,101210

となりました。

JAVAではgetBytesという方法で操作しましたので
1レコードの末尾を気にすることなく???
出来ると思うのですが
現在あるVBAの方が
仕様により頭に4byteある可変長データの場合とか
ありますし、また
javaで考えるより
VBAの方が検証せずに安心かなと・・・
バイナリのデータに関して
対応できない場合ご相談させてください。
どうもありがとうございました。

(フェンダー)


 できたら、実行すれば、手直しせずに作動するプログラムの提示が見たかったのですが・・・。
 まっ、でもおおよそはわかりました。

 因みにサンプル作成用のコードを

 標準モジュールに

 '======================================================================
 Option Explicit
 Type rec
   rnk   As String * 3
   nm As String * 8
   vl As String * 6
 End Type
 Sub mk_sample()
    Dim fno As Long
    Dim dat As rec
    Dim g0 As Long
    Const rcnt = 100000 'ここの数を変えると、容量の違うファイルが作成できます
    '↑この状態だと30万件の固定長レコードが作成されます
    On Error Resume Next
    Kill ThisWorkbook.Path & "\sample.dat"
    On Error GoTo 0
    fno = FreeFile
    Open ThisWorkbook.Path & "\sample.dat" For Random As #fno Len = Len(dat)
    For g0 = 1 To rcnt
       With dat
          .rnk = Format(1, "000")
          .nm = "指原莉乃"
          .vl = CStr(150570)
       End With
       Put #fno, , dat
       With dat
          .rnk = Format(2, "000")
          .nm = "大島優子"
          .vl = CStr(136503)
       End With
       Put #fno, , dat
       With dat
          .rnk = Format(3, "000")
          .nm = "渡辺麻友"
          .vl = CStr(101210)
       End With
       Put #fno, , dat
    Next
    Close #fno
 End Sub

尚、一度適当な場所に保存後に実行してください。

 コードを拝見して 気になったことです。

1 Public変数の使用

     Public  連番(1 To 3) As Byte 
     Public  氏名(1 To 8) As Byte 
     Public 投票数(1 To 6) As Byte 
     Public h0D0A(1 To 2) As Byte

    Public変数を コードとは別のモジュールに宣言していますが、これは止めた方がよいです。
    CSV変換のプログラム内で 連番という配列は、ファイルから読み込むための変数ですよね?
    これを他のモジュールに宣言してコードがあるモジュールと変数を宣言したモジュールの関わりを
    強くする意味がありません。
    この変数は、CSV変換するプロシジャーの中で使うだけですから、プロシジャー内に宣言すれば
    よいですね!!

 よって、

 標準モジュールに

 '=================================================================
 Option Explicit
 Sub Main()
    Dim 連番(1 To 3) As Byte
    Dim 氏名(1 To 8) As Byte
    Dim 投票数(1 To 6) As Byte
    Dim h0D0A(1 To 2) As Byte '1レコードの末尾に&h0D0Aがある場合    
    Dim InFileName As String
    Dim OutFileName As String
    Dim InFileName As String
    Dim InFN As Long '入力ファイルハンドル番号
    Dim OutFN As Long '出力ファイルハンドル番号
    Dim InFileLen As Long '入力ファイルのサイズ
    Dim InPOS As Long '入力ファイル位置
    Dim OutPOS As Long '出力ファイル位置
    h0D0A(1) = &HD
    h0D0A(2) = &HA
    On Error Resume Next
    Kill OutFileName
    On Error GoTo 0
    InFileName = ThisWorkbook.Path & "\SAMPLE.dat"
    OutFileName = ThisWorkbook.Path & "\SAMPLE.csv"
    InFN = FreeFile
    Open InFileName For Binary As #InFN
    OutFN = FreeFile
    Open OutFileName For Binary As #OutFN
    InPOS = 1
    OutPOS = 1
    InFileLen = LOF(InFN)
    Do While (InPOS < InFileLen)
       Get #InFN, InPOS, 連番
       Put #OutFN, OutPOS, 連番
       InPOS = InPOS + 3
       Put #OutFN, , ","
       OutPOS = OutPOS + 4
       '************************
       '2フィールド******************
       Get #InFN, InPOS, 氏名
       Put #OutFN, OutPOS, 氏名
       InPOS = InPOS + 8
       OutPOS = OutPOS + 9
       Put #OutFN, , ","
       '************************
       '3フィールド******************
       Get #InFN, InPOS, 投票数
       Put #OutFN, OutPOS, 投票数
       InPOS = InPOS + 6
       OutPOS = OutPOS + 6
       '************************
       '1レコードの末尾&h0D0A*************
       Get #InFN, InPOS, h0D0A
       Put #OutFN, OutPOS, h0D0A
       InPOS = InPOS + 2
       OutPOS = OutPOS + 2
    Loop
    Close #InFN
    Close #OutFN
    Erase 連番()
    Erase 氏名()
    Erase 投票数()
    Erase h0D0A()
 End Sub

2 Get及び、Putの書式

    GetやPutステートメントの書式が読み込み位置や書き込み位置を
    指定した書式や指定しない書式が混在しています。

    >Put #OutFN, OutPOS, 氏名
    >Put #OutFN, , ","

    GetやPutステートメントは、読み込み又は、書き込み位置を指定しなければ、
    順次 読み込み 書き込みをしてくれます。

    又、指定しない方が若干でも処理は速いですよ!!

  先のコードは、

 '=======================================================================
 Sub Main2()
    Dim 連番(1 To 3) As Byte
    Dim 氏名(1 To 8) As Byte
    Dim 投票数(1 To 6) As Byte
    Dim h0D0A(1 To 2) As Byte '1レコードの末尾に&h0D0Aがある場合
    Dim InFileName As String
    Dim OutFileName As String
    Dim InFileName As String
    Dim InFN As Long '入力ファイルハンドル番号
    Dim OutFN As Long '出力ファイルハンドル番号
    Dim InFileLen As Long '入力ファイルのサイズ
    Dim g0 As Long
    h0D0A(1) = &HD
    h0D0A(2) = &HA
    InFileName = ThisWorkbook.Path & "\SAMPLE.dat"
    OutFileName = ThisWorkbook.Path & "\SAMPLE.csv"
    On Error Resume Next
    Kill OutFileName
    On Error GoTo 0
    InFN = FreeFile
    Open InFileName For Binary As #InFN
    OutFN = FreeFile
    Open OutFileName For Binary As #OutFN
    InFileLen = LOF(InFN)
    For g0 = 1 To LOF(InFN) / 17
       Get #InFN, , 連番
       Put #OutFN, , 連番
       Put #OutFN, , ","
       '2フィールド******************
       Get #InFN, , 氏名
       Put #OutFN, , 氏名
       Put #OutFN, , ","
       '************************
       '3フィールド******************
       Get #InFN, , 投票数
       Put #OutFN, , 投票数
       '************************
       '1レコードの末尾&h0D0A*************
       Get #InFN, , h0D0A
       Put #OutFN, , h0D0A
    Next
    Close #InFN
    Close #OutFN
    Erase 連番()
    Erase 氏名()
    Erase 投票数()
    Erase h0D0A()
 End Sub

 と書き換えることができます。

3 Get Putの使用頻度

 最近は、ファイルの読み書きも結構速くなってきていますが、
 一般的には、プログラム全体から見て、ファイルの読み書きは、速度が比較的遅くなる
 箇所です。よって、処理速度向上のため、なるべく、Get及び、Putの使用頻度を減らすことを考えます。

 例えば、一回にGetでファイルのすべてのデータを読み込み、csvファイル用に
 データを編集し、一回のPut(または、Print等)ですべてデータを書き込む

 このようにプログラムを作成すれば、Get、PutというファイルI/Oは、一回ずつで
 済むので処理速度は、一般的には、速くなります。

 が、ファイルの容量が莫大な場合は、一度に全部のデータを読み込む方法だと
 メモリ不足を起こす可能性がありますから、ファイル容量と相談する必要があります。
 OSのレコードとバッファの関係をコード内でシミュレートして効率よく
 ファイルデータを読み書きする方法もありますが、
 1レコード読み込み、編集、1レコード書き込みに変えるだけでも
 処理速度の向上は、ありますよ!!

 '=============================================================================
 Option Explicit
 Type indat
    b1(1 To 3) As Byte
    b2(1 To 8) As Byte
    b3(1 To 6) As Byte
 End Type
 Sub test3()
    Dim f1 As Integer
    Dim f2 As Integer
    Dim rec As indat
    Dim g0 As Long
    Dim odat(1 To 3) As String
    On Error Resume Next
    f1 = FreeFile
    Open ThisWorkbook.Path & "\SAMPLE.dat" For Random As #f1 Len = LenB(rec)
    f2 = FreeFile
    Open ThisWorkbook.Path & "\SAMPLE.csv" For Output As #f2
    For g0 = 1 To LOF(f1) / Len(rec)
        Get #f1, , rec
        odat(1) = StrConv(rec.b1, vbUnicode)
        odat(2) = StrConv(rec.b2, vbUnicode)
        odat(3) = StrConv(rec.b3, vbUnicode)
        Print #f2, Join(odat(), ",")
    Next
    Close #f2
    Close #f1
  End Sub

 先の mk_sampleの

 const rcnt=1000000  百万ぐらいでファイルを作成して、

 処理速度を比べてみてください。

追伸

EOF関数を使ったコードを拝見しましたが、
このEOF関数、ファイルをオープンするモードによって、Trueを返すタイミングが違いますから、
注意してください。


 Sub Eof_test()
    Dim fno As Long
    Dim dat As Byte
    fno = FreeFile
    Open ThisWorkbook.Path & "\sample2.dat" For Output As #fno
    Close #fno
    MsgBox "空のファイル 「Sample2.Dat」を作成しました" & vbCrLf & _
           "これを Inputモードでオープンすると・・・・"
    fno = FreeFile
    Open ThisWorkbook.Path & "\sample2.dat" For Input As #fno
    MsgBox "オープン直後にEOF(fno)の値は  " & EOF(fno)
    Close #fno
    MsgBox "次にBinaryモードでオープンすると・・・・"
    fno = FreeFile
    Open ThisWorkbook.Path & "\sample2.dat" For Binary As #fno
    MsgBox "オープン直後にEOF(fno)の値は  " & EOF(fno)
    Get #fno, , dat
    MsgBox "一度Getステートメント実行後のEOF(fno)の値は  " & EOF(fno)
    Close #fno
 End Sub

このコードも一度保存後に実行してください。

 私の感覚からすると、Binaryモード時のEOFの値の推移の方が慣れしたんだ
 手順ですが・・・・。

 以上、気になったことを投稿しました。

 ichinose


  >Public変数を コードとは別のモジュールに宣言していますが、これは止めた方がよいです。
  >CSV変換のプログラム内で 連番という配列は、ファイルから読み込むための変数ですよね?

わざわざサンプルデータも添えて頂き
アドバイスどうもありがとうございます。
こちらはサンプルになりますので、
カンマ以外の書き込みは想定しておりません。
仕様により、1レコードの配列(フィールド)が
1000近く存在する場合もありますので(通常が100〜150)
そうなると別のモジュールに宣言し2つのモジュールで
分けた方が管理しやすいと
いった理由でしたので了解致しました。

JAVAは基本的に
件数が多いデータを早く処理したい為に
使っているのですがと発言してしまったのですが
現状の件数ですとすいませんがこちらはそれほど重要ではありません。

現在のプログラムで100万件ほどの処理を検証済みですが
1レコードの長さによりますが(1〜5分で済みます)
固定長データよりCSVのプログラムの方が若干遅いかな?ぐらいで済んでます。
それよりかはKEIS固定長データからCSVに生成する
システムの方が100万件ですと
30分近くかかりますのでそれから比べたらぜんぜん速いです。

私がVBAに応用が利かないこともありますので
どうせ初めから勉強するなら、オブジェクト指向の言語を
覚えようというぐらいの理由になります。
じゃーなぜVBAも使うの?と言われそうですが
効率の問題で、今ある土台のプログラムは効率の為
使用した方が良いという理由があります。
それと徐々にですが、時間がある時に勉強の為調べながら
(コピーバンドのように)
VBAのプログラムをJAVAに置き換えてますという感じです。

一応JAVAでは、Bufferクラスを使用して1行ずつ読み書き対応出来るようにして
処理は早くなってますが
今の仕事のボリューム内容でしたら
現在のVBAプログラムで対応出来ます。

〉EOF関数を使ったコードを拝見しましたが、

私は今回の仕様でEOF関数は使用したことはありませんが
こちらに関しては知見はありませんでした。
例にならい、今後注意いたします。

(フェンダー)


処理速度の件でアドバイス頂きましたので
ご報告だけさせていただきます。

VBAver

Sub Main()

Dim InFileName As String
Dim OutFileName As String

Dim InFN As Long '入力ファイルハンドル番号
Dim OutFN As Long '出力ファイルハンドル番号
Dim InFileLen As Long '入力ファイルのサイズ
Dim InPOS As Long '入力ファイル位置
Dim OutPOS As Long '出力ファイル位置

InFileName = "\sample2.dat"
OutFileName = "\sampleout2.dat"
InFN = FreeFile
Open InFileName For Binary As #InFN
OutFN = FreeFile
Open OutFileName For Binary As #OutFN

InPOS = 1
OutPOS = 1
InFileLen = LOF(InFN)
iTitle = 6 'チェック用 エクセルシートのタイトルの行位置
ipos = 7 'チェック用 エクセルシートのデータの行位置・開始

Do While (InPOS < InFileLen)

'1フィールド*******************

  Get #InFN, InPOS, 連番

  Put #OutFN, OutPOS, 連番

  InPOS = InPOS + 3
  OutPOS = OutPOS + 4
  Put #OutFN, , ","
'************************

'2フィールド******************

  Get #InFN, InPOS, 氏名

  Put #OutFN, OutPOS, 氏名

  InPOS = InPOS + 8
  OutPOS = OutPOS + 9
  Put #OutFN, , ","
'************************

'3フィールド******************

  Get #InFN, InPOS, 投票数

  Put #OutFN, OutPOS, 投票数

  InPOS = InPOS + 6
  OutPOS = OutPOS + 6
'************************

'1レコードの末尾&h0D0A*************

  Get #InFN, InPOS, h0D0A

  Put #OutFN, OutPOS, h0D0A

  InPOS = InPOS + 2
  OutPOS = OutPOS + 2
'************************

  ipos = ipos + 1
Loop

Close #InFN
Close #OutFN

End Sub

Public 連番(1 To 3) As Byte
Public 氏名(1 To 8) As Byte
Public 金額(1 To 6) As Byte
Public h0D0A(1 To 2) As Byte '1レコードの末尾に&h0D0A

を下記でjavaに書き換えました。

import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;

import java.io.ByteArrayOutputStream;

/**

 * ファイルの書き込みサンプル
 *バイナリーデータの読み書き
 */
public class dat_from_CSV {

  public static void main(String[] args) {

    // 読み込むファイルの名前
    String inputFileName = "\\SAMPLE.dat";

    // 書き込むファイルの名前
    String outputFileName = "\\SAMPLEout.csv";

    // ファイルオブジェクトの生成
    File inputFile = new File(inputFileName);
    File outputFile = new File(outputFileName);

    try {
      // 入力ストリームの生成
      FileInputStream fis = new FileInputStream(inputFile);
      BufferedInputStream bis = new BufferedInputStream(fis);

      // 出力ストリームの生成
      FileOutputStream fos = new FileOutputStream(outputFile);
      BufferedOutputStream bos = new BufferedOutputStream(fos);

     //読み込み用バイト配列◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎   
      byte[] inbuf = new byte[19];
     //読み込み用バイト配列◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎ 

      String b = new String(inbuf);//byte配列から文字列への変換
      String tmpstr;
      byte[] kanma = ",".getBytes("Shift_JIS");  //カンマ書き込み

      // ファイルへの読み書き
      int len = 0;

    //読み込み用バイト配列◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎ 
      while ((len = bis.read(inbuf, 0, 19)) == 19) {
    //読み込み用バイト配列◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎ 	

    //*********************************************************************
    //1フィールド  連番

    	byte[] outbuf1=new byte[3] ;
    	outbuf1[0] = inbuf[0] ;
    	outbuf1[1] = inbuf[1] ;
    	outbuf1[2] = inbuf[2] ;
    	bos.write((outbuf1));

    	//bos.write(inbuf, 0, 3); //inbufから直接書き込む場合
    	bos.write(kanma);
    //*********************************************************************
    //2フィールド  氏名

    	byte[] outbuf2=new byte[8] ;
    	outbuf2[0] = inbuf[3] ;
    	outbuf2[1] = inbuf[4] ;
    	outbuf2[2] = inbuf[5] ;
    	outbuf2[3] = inbuf[6] ;
    	outbuf2[4] = inbuf[7] ;
    	outbuf2[5] = inbuf[8] ;
    	outbuf2[6] = inbuf[9] ;
    	outbuf2[7] = inbuf[10] ;
    	bos.write((outbuf2));

    	//bos.write(inbuf, 3, 8); //inbufから直接書き込む場合
    	bos.write(kanma);
    	//bos.write(unpack(new byte[]{0x01, 0x2F}));   //outbuf2));

    //*********************************************************************
    //3フィールド   投票数

    	byte[] outbuf3=new byte[6] ;
    	outbuf3[0] = inbuf[11] ;
    	outbuf3[1] = inbuf[12] ;
    	outbuf3[2] = inbuf[13] ;
    	outbuf3[3] = inbuf[14] ;
    	outbuf3[4] = inbuf[15] ;
    	outbuf3[5] = inbuf[16] ;
    	bos.write((outbuf3));

    	//bos.write(inbuf, 11, 6); //inbufから直接書き込む場合
    	//bos.write(ODRWeight1(outbuf3));

    //*********************************************************************
    //1レコードの末尾&h0D0A
  	byte[] outbuf4=new byte[2] ; 
  	outbuf4[0] = inbuf[17] ;
  	outbuf4[1] = inbuf[18] ;

  	bos.write(outbuf4);}
    //*********************************************************************

   //*********************************************************************

      // 後始末
      bos.flush();
      bos.close();
      bis.close();

    // エラーがあった場合は、スタックトレースを出力
    } catch(Exception e) {
      e.printStackTrace();
    }

  }

因みに念のため下記が改行なしver &ソース短縮

import java.io.BufferedInputStream;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;

import java.io.ByteArrayOutputStream;

/**

 * ファイルの書き込みサンプル
 *バイナリーデータの読み書き
 */
public class dat_from_CSV改行なしver {

  public static void main(String[] args) {

    // 読み込むファイルの名前
    String inputFileName = "\\SAMPLE.dat";

    // 書き込むファイルの名前
    String outputFileName = "\\SAMPLEout.csv";

    // ファイルオブジェクトの生成
    File inputFile = new File(inputFileName);
    File outputFile = new File(outputFileName);

    try {
      // 入力ストリームの生成
      FileInputStream fis = new FileInputStream(inputFile);
      BufferedInputStream bis = new BufferedInputStream(fis);

      // 出力ストリームの生成
      FileOutputStream fos = new FileOutputStream(outputFile);
      BufferedOutputStream bos = new BufferedOutputStream(fos);

     //読み込み用バイト配列◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎   
      byte[] inbuf = new byte[17];
     //読み込み用バイト配列◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎ 

      String b = new String(inbuf);//byte配列から文字列への変換
      String tmpstr;
      byte[] kanma = ",".getBytes("Shift_JIS");  //カンマ書き込み
      byte[] 改行 ="\r\n".getBytes("Shift_JIS");  //カンマ書き込み

      // ファイルへの読み書き
      int len = 0;

    //読み込み用バイト配列◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎ 
      while ((len = bis.read(inbuf, 0, 17)) == 17) {
    //読み込み用バイト配列◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎◎ 	

    //*********************************************************************
    //1フィールド 連番

    	bos.write(inbuf, 0, 3); //inbufから直接書き込む場合
    	bos.write(kanma);
    //*********************************************************************
    //2フィールド 氏名

    	bos.write(inbuf, 3, 8); //inbufから直接書き込む場合
    	bos.write(kanma);
    	//bos.write(unpack(new byte[]{0x01, 0x2F}));   //outbuf2));

    //*********************************************************************
    //3フィールド 投票数

    	//bos.write(inbuf, 11, 6); //inbufから直接書き込む場合
    	bos.write(改行);}

    //*********************************************************************

   //*********************************************************************

      // 後始末
      bos.flush();
      bos.close();
      bis.close();

    // エラーがあった場合は、スタックトレースを出力
    } catch(Exception e) {
      e.printStackTrace();
    }

  }

1レコードの末尾&h0D0Aの事は知りませんでしたので
(その後バイナリエディタでチェックして分かりましたが・・・)
大変勉強になりました。

(フェンダー)


データが100万行くらいあるとのことでしたので、高速化のアドバイス。

VBAでもJAVAでも同じですが、I/O回数を減らすことで、大きく性能向上を狙えます。
1行のサイズは小さいようなので、1000行まとめて読み、同じようにまとめて
書くことを考えてみると良いでしょう。
(1行分のbyte配列を、2次元配列にして、1000行分にすれば良い)

大元データをCSV化するのに30分くらいとの事でしたが、こちらも作成者に、
「1000行分まとめてI/Oすれば?」と伝えると、1分くらいで終わるように思います。
(???)


 >大元データをCSV化するのに30分くらいとの事でしたが、こちらも作成者に、 
 >「1000行分まとめてI/Oすれば?」と伝えると、1分くらいで終わるように思います。 

 アドバイスどうもありがとうございます。
 プログラムに関しては検討しまして
 システム会社に相談してみます。

 (フェンダー)

 ちょっと訂正、というかバグです。

 前回投稿のプロシジャー Main と Main2

 Main

       >'1レコードの末尾&h0D0A*************
       >Get #InFN, InPOS, h0D0A
       >Put #OutFN, OutPOS, h0D0A
       >InPOS = InPOS + 2
       >OutPOS = OutPOS + 2

 は、

       '1レコードの末尾&h0D0A*************
       Put #OutFN, OutPOS, h0D0A
       OutPOS = OutPOS + 2

 訂正してください。
 読み込み側には、 改行コードはないので・・・・。

 Main2

      >'1レコードの末尾&h0D0A*************
      > Get #InFN, , h0D0A
      > Put #OutFN, , h0D0A

 は、

      '1レコードの末尾&h0D0A*************
       Put #OutFN, , h0D0A

 と訂正してください。

 尚、プロシジャーmk_sample内の

 cnt=1000000 '(実質3百万レコード)

 とcntという定数の値を上記で設定し、作成した Sample.datで実験した場合の処理速度ですが、
 私の環境で

 Main ------  53秒
 Main2 ------    44秒
 test3 ------    15秒

 でした。

 ichinose


 コメントどうもありがとうございます。

 ichinoseさんの環境と比較して

 Main ------  53秒
 Main2 ------    44秒
 test3 ------    15秒

 私の環境で検証させて頂きましたところ

 Main ------  1分40秒
 Main2 ------    1分40秒
 test3 ------       29秒

 処理速度は遅いですがtest3 のコードですと
 かなり速度が向上することが
 確認できました。

因みに
ichinoseさんのコードから作成したSample.datを
JAVAで入出力しますと
1秒でした。
バイナリデータや可変長データの処理を考えると
VBAの処理で対応する可能性が高いですが・・・

※実際テストデータを授受した際
新規でJAVAでソースが書けるか分かりませんので・・・

ichinoseさんのSAMPLE.datサイズは48.6MB
最近の仕事で1レコードの長さから作成した
約100万件のSAMPLE.datですとサイズが820MBになります。
参考までに・・・

(フェンダー)
\test.dat


 以下のVBAコードで前回と同じ容量のsample.datを入力データとして、実行した時の
 処理時間は、私の環境で 2.4秒というところでした。

 '===============================================================
 Sub test4()
    Dim f1 As Long
    Dim g0 As Long
    Dim g1 As Long
    Dim g2 As Long
    Dim ind() As Byte
    Dim oud() As Byte
    Dim tm As Double
    tm = [now()]
    On Error Resume Next
    tm = [now()]
    Kill ThisWorkbook.Path & "\sample.csv"
    On Error GoTo 0
    f1 = FreeFile
    Open ThisWorkbook.Path & "\sample.dat" For Binary As #f1
    ReDim ind(LOF(f1) - 1)
    Get #f1, , ind()
    Close #f1
    ReDim oud((UBound(ind) + 1) / 17 * 21 - 1)
    g0 = 0: g1 = 0
    Do While g0 <= UBound(ind)
       For g2 = g0 To g0 + 2
          oud(g1) = ind(g2)
          g1 = g1 + 1
       Next
       oud(g1) = Asc(",")
       g0 = g2
       g1 = g1 + 1
       For g2 = g0 To g0 + 7
          oud(g1) = ind(g2)
          g1 = g1 + 1
       Next
       oud(g1) = Asc(",")
       g0 = g2
       g1 = g1 + 1
       For g2 = g0 To g0 + 5
          oud(g1) = ind(g2)
          g1 = g1 + 1
       Next
       oud(g1) = &HD
       oud(g1 + 1) = &HA
       g0 = g2
       g1 = g1 + 2
    Loop
    f1 = FreeFile
    Open ThisWorkbook.Path & "\sample.csv" For Binary As #f1
    Put #f1, , oud()
    Close #f1
    MsgBox Application.Text([now()] - tm, "hh:mm:ss.00")
 End Sub

 これ以上、容量の大きいファイルは、一度の読み込みでは心配ですから、
 BufferedOutputStreamやBufferedInputStreamのようなことをVBAで自作する必要が
 ありそうです。

 ichinose


コード提示どうもありがとうございます。

自宅の環境ですと2.11秒でした。
かなり速いですね。
VBAですとLine Inputステートメントというもので
読み込みだけは1行ずつ対応出来そうですけどね。

下記の処理はネット環境があるPCで
確認したのですが
実際は個人情報ですのでネット環境がないPCで
作業しますので試せていたいのですが・・・
環境にもよりますよね。

 Main ------    1分40秒
 Main2 ------    1分40秒
 test3 ------       29秒

因みに今回テストしている固定長→CSVより
CSV入出力の方が内容が濃いのですが
Bufferedクラスで310MBのファイルを試したとこ
30秒ぐらいで
VBAですと2分ぐらいというところでしょうか。
MsgBoxで時間を表示させたわけじゃないので
だいたいの時間ですが・・・

速度よりは安全に処理できるかという方が大事だと
思いますけど。

(フェンダー)


 >VBAですとLine Inputステートメントというもので 
 >読み込みだけは1行ずつ対応出来そうですけどね

 このスレッドの最初の方のフェンダーさんの発言に

 >データは区切りのないファイルとなります。

 という記述がありました。
 このようなファイルは、
 Line Inputステートメントでは、1行毎にデータの取得はできませんよ!!
 そもそも1行がどこまでなのかがわかりませんし・・・。
 Line Input ステーメントは区切り(&H0D0A)コードがあるという前提です。

 区切りコードのないデータは、バイナリモードかランダムモードでOpenして、
 Getステートメントで読み込みが一般的です。

 データ内にバイナリデータもある可能性があるなら、尚のことです。
 Line Inputでは、正確にバイナリデータを取得することは難しいですから・・・。
 他にもADOのStreamオブジェクトを使う方法もあります。
 時間がある時に調べておけば役に立ちます。

 >VBAですと2分ぐらいというところでしょうか。 
 Javaの方がVBAより、処理速度は速いはずですが、工夫をすれば、少しでもその差を縮められる
 という例として、今回、同じ処理をするコードをコードを変えて何度か投稿しました。

 よって、ファイルの種類が違ってもVBAでも工夫しだいで2分かかる処理速度を縮めることは
 出来るかもしれませんよ!!

 >速度よりは安全に処理できるかという方が大事だと 
 >思いますけど。

 これは、賛成です。但し、処理速度の速いアルゴリズムを知っていて、習得した上で
 検討した結果、わかりやすいアルゴリズムや簡単な手法を選択する という判断であるならばですよね!!
 勉強をしない免罪符にはしないでください

 ichinose


失礼しました。
Line インプットステートメントを
よく知らずに発言してしまいました。
 そもそもichinoseさんなら、コード提示頂いてる時点で
気づいてますから。
それと、どんなバイナリデータが
混在するか分かりませんからね。
今は、ソースコードの勉強もそうですが
データ構造を勉強したいと思っております。
因みに工夫すれば安全に速く処理出来る方法も
あるかと思いますが
VBAは、難易度が高いのとアップデートがない分?
現状は自作するのは非常に難しいです。
それと以前在籍してた
プログラマーが
KEIS等扱うgetステートメントより
AdodbによるCSV入出力の方が
一度に読み込んで書き出す作業は
心配と言ってたのですが
 ichinoseさんはこの発言について
なにか分かりますでしょうか?

フェンダー


 >VBAは、難易度が高いのとアップデートがない分?
 >現状は自作するのは非常に難しいです。

 この点について・・・。
 >アップデートがない分?
 この意味がわかりませんが・・。

 私も独学でプログラミングを始めたのであれば、おそらくは、誰も教えてくれないので
 中々ファイルI/Oの高速化の自作なんて発想にはなりません。
 が、今はこのような掲示板があるのですから、色んな手法をご存じの方がいるはずです。
 このスレッドでも

 >3 Get Putの使用頻度 

 という投稿で記述しましたが、

 Get PUTの使用頻度を減らすことを考える
 と申し上げました。

 そのために
 >レコードとバッファの関係をコード内でシミュレート
 と記述しました。

 >一応JAVAでは、Bufferクラスを使用して1行ずつ読み書き対応出来るようにして 
 >処理は早くなってますが

 このBufferクラスは、1行で読み込めるから処理が速いのではなく、直接ファイルにアクセスする
 回数をこの中では極端に少なくしているからです。
 オブジェクトという形式で中身を隠蔽していて指定バイトで読み込み書き込みという
 インターフェースになっているので、一見、
 bos.writeでこのコードの回数分ファイルに書き込みしているようにみえますが、書き込み先は
 実際は、ファイルではないと思いますよ。

 最後に投稿したコードは、これの真似事みたいなコードを投稿したつもりです。
 もっと、高速な方法、VBAで自作のオブジェクトを作成すれば、もっとスッキリしたコードに
 なる可能性はあります。

 まずは、得意なJAVAのこのBufferクラスで何をしているから高速なのかを探ってみては
 いかがですか?

 >AdodbによるCSV入出力の方が
 >一度に読み込んで書き出す作業は
 >心配と言ってたのですが

 扱うCSVファイルの容量の問題(ファイル容量が大きい)ではないですか?
 それから派生する問題は、試してみなければわかりませんね!!
 >CSVより CSV入出力の方が内容が濃いのですが
 CSVの入出力の具体的な内容にもよりますし・・・。

 但し、前回投稿の
 >他にもADOのStreamオブジェクト

 は、大丈夫だと思いますけどねえ

 実は、これを使ってのSample.DAT〜Sample.csvの作成は、私の環境で7〜8秒でした。

 インタフェースは、BufferedInputStream等に似ているので扱いやすいかもしれませんよ!!
 もっともこれほど速くはないですが・・・。

 以上です。

 ichinose


 アドバイスどうもありがとうございます。
 ひとまずこのスレのおかげで
 表題の土台は解決したと思います。
 色々な忠告を受け止めて
 勉強してみます。
 このスレ長いですよね。
 どうもありがとうございました。

 (フェンダー)


 こちらはエクセル学校でありますので
 こういった説明はしないつもりでしたが
 ご報告いたします。

 >一応JAVAでは、Bufferクラスを使用して1行ずつ読み書き対応出来るようにして 
 >処理は早くなってますが

 こちらは細かく説明しなかったのですが
 サンプル提示したものとは違います。
 ichinoseさんが引っかかった部分は
 CSV入出力で、実績があるソースコード(今回は標題と関係ありませんから提示してませ ん)で
 BufferedReaderクラスには1行ずつデータを読み込む、
 readLineメソッドが用意されていて
 writeメソッドによりデータの書き込み処理を行なっています。

 今回、使用し提示したソースコードはバイナリ入出力の
 ソースコードになりますので
 1行ずつ読み書き
 1行ずつ読み
 そして書くの手順では実行されてないです。
 こちらの処理方法手順は記載しません。

 >一応JAVAでは、Bufferクラスを使用して1行ずつ読み書き対応出来るようにして 
 >処理は早くなってますが

 とは関係ないことだけご報告いたします。
 提示したサンプルコードについては
 検証段階で実績はありません。

 それと他にも
 紛らわしい情報や
 色々と疑問がある場合もあるかと思いますが
 ご了承ください。
 標題は一段落してますので見て頂くだけで返信はいりません。
 今回の件についてはご報告させていただきました。

(フェンダー)


 > 今回、使用し提示したソースコードはバイナリ入出力の
 >ソースコードになりますので
 >1行ずつ読み書き
 >1行ずつ読み
 >そして書くの手順では実行されてないです

 それは、コードを拝見したのでわかっているつもりです。

 >CSV入出力で、実績があるソースコード(今回は標題と関係ありませんから提示してませ ん)で
 >BufferedReaderクラスには1行ずつデータを読み込む、
 >readLineメソッドが用意されていて

 どっちにしろ、私が申し上げたいことは同じなんですよ!!

 BufferedReaderクラスのreadLineメソッドは、
 >1行で読み込めるから処理が速いのではなく、直接ファイルにアクセスする
 >回数をこの中では極端に少なくしているからです。

 と前回投稿と同じことは言えるのです。
 この中のしくみは、

 何度も繰り返しますが、

 ファイルへの読み込み、書き込みを減らすことを考えた手法がとられているのです。

 つまり、何回も申し上げているVBAで言い換えれば、

 >Get PUTの使用頻度を減らすことを考える

 なのです。

 ですから、

 >このBufferクラスで何をしているから高速なのかを探ってみては

 と記述しました。私が申し上げたいのは ここなのです。

http://www.javaroad.jp/java_io4.htm

 ここの概要に私が申し上げたようなことがかいてありますから、読んでみてください。

 そして、理解したら、VBAでコードを書くなら、同じようなクラスを作ってみては?
 という提案です。

 このスレッドでの私が言いたかったことですから、返信不要と仰っていましたが、
 敢えて投稿しました。

 ichinose


 >1行で読み込めるから処理が速いのではなく、直接ファイルにアクセスする
 >回数をこの中では極端に少なくしているからです。

 こちらもわざわざ細かく説明しなかったのですが
 http://www.javaroad.jp/java_io4.htm

 もJAVAを始める以前からチェックしていたサイトで
 把握はしていて使用していました。

 もちろんサイトだけチェックするぐらいでは
 ソースも書けないので、勉強している段階になりますが。

 勉強段階ですし、わざわざうんちくを語るように細かく説明するつもりはありませんでしたので
 下記のような
 >一応JAVAでは、Bufferクラスを使用して1行ずつ読み書き対応出来るようにして

 >処理は早くなってますが

 と失言をしてしまったのが始まりですが・・・
 以後気をつけます。
 
 そもそもJAVAにはこういった効率がよく入出力できる
 クラスがあるというのを知ってから
 使用するのがきっかけになりますから。
 文字コードの指定もクラスによって自由に変更出来たりと。
 まあ開発統合環境でも文字コードは指定できますが・・・
 あまり適当に喋りますと失言が出てしまいますのでこの辺で・・・

 >そして、理解したら、VBAでコードを書くなら、同じようなクラスを作ってみては?
 >という提案です。

 結論から申し上げますと、今回の開発はJAVAで行うことにしました。
 VBAでクラスを考える時間で、今後想定される仕様の
 勉強に時間をあてた方が良いかと思いまして・・・
 色々、サンプル提示していただいたVBAコードは
 他のアイデアでも参考に出来ますので大変勉強になりました。
 フィールドごとに考えなければいけないことが
 ありましたら、
 アルゴリズムについてのアドバイスを
 頂けたら助かります。
 その時はどうぞ宜しくお願いいたします。

 (フェンダー)


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.