● VB で保存した数値データを Java で読む ●

その昔 Jukebox というツールを Java で作ったとき見舞われた現象。VB で作成したごった煮バイナリファイルから Long 型で保存したデータを読むことが出来なかったのさ。その覚え書き。

当方 Java をほとんど知らないので、ここに書いてあることは何処まで真実かは測りかねない。

(1)まず VB で適当なテキストデータを作成する。
 
Private Type SAMPLE
    ByteValue As Byte
    IntValue As Integer
    LongValue As Long
End Type

Private Sub Form_Load()

    Dim udtSample As SAMPLE
    Dim FileNum As Integer
    Dim FileData() As Byte
    Dim FileSize As Long
    Dim i As Long

    With udtSample
        .ByteValue = 255
        .IntValue = 30362
        .LongValue = 426435223
    End With

    '構造体に設定した値を保存
    FileNum = FreeFile
    Open "C:\Test.txt" For Binary As #FileNum
        Put #FileNum, , udtSample
    Close #FileNum

    '保存したファイルからデータを取得
    FileNum = FreeFile
    Open "C:\Test.txt" For Binary Access Read As #FileNum
        FileSize = LOF(FileNum)
        ReDim FileData(LOF(FileNum) - 1) As Byte
        FileData() = InputB(FileSize, #FileNum)
    Close #FileNum

    For i = 0 To UBound(FileData)
        Debug.Print Format$(Hex$(FileData(i)), "00");
        If i < UBound(FileData) Then Debug.Print ",";
    Next i

    '構造体に値を取得
    FileNum = FreeFile
    Open "C:\Test.txt" For Binary As #FileNum
        Get #FileNum, , udtSample
    Close #FileNum
    With udtSample
        Debug.Print ""
        Debug.Print CStr(.ByteValue)
        Debug.Print CStr(.IntValue)
        Debug.Print CStr(.LongValue)
    End With
End Sub

[イミディエイトウィンドウの表示結果]
FF,9A,76,97,E2,6A,19
255
30362
426435223
(2)VB で上記データを自力で復元する
 
'1バイトの最大値は256、要するに256進数で桁計算すればよい
Debug.Print "Byte:" & CStr(&HFF)
Debug.Print "Integer:" & CStr(&H9A + &H76 * (2 ^ 8))
Debug.Print "Long:" & _
           CStr(&H97 * (256 ^ 0) + &HE2 * (256 ^ 1) + _
                &H6A * (256 ^ 2) + &H19 * (256 ^ 3))

[イミディエイトウィンドウの表示結果]
Byte:255
Integer:30362
Long:426435223
(3)Javaでファイルデータを読んで数値を取得する
 
import java.io.*;

public class ReadFile {

    //コードの量を抑えるため、main() で例外を投げている↓
    public static void main(String argv[]) throws Exception {
        ReadFile rf = new ReadFile();
        rf.execute("C:/Test.txt");
    }

    public void execute(String readFile) throws Exception {
        DataInputStream in = 
               new DataInputStream(new FileInputStream(readFile));
        System.out.println(in.readByte());
        System.out.println(in.readShort());
        System.out.println(in.readInt());
        in.close();
    }

}

[実行結果]
-1
-25994
-1746769383
↑何じゃこりゃあぁ!!
(4)とりあえず気を取り直して、Javaでバイトデータを読み取る
 
import java.io.*;

public class ReadFile {

    public static void main(String argv[]) throws Exception {
        ReadFile rf = new ReadFile();
        rf.execute("C:/Test.txt");
    }

    public void execute(String readFile) throws Exception {
        DataInputStream in = 
               new DataInputStream(new FileInputStream(readFile));
        for(int i=0;i<7;i++){
            System.out.print(
               Integer.toString(in.readByte(), 16).toUpperCase());
            if(i < 6) System.out.print(",");
        }
        in.close();
    }

}

[実行結果]
-1,-66,76,-69,-1E,6A,19
↑再び、何じゃこりゃあぁ!!

バイトデータ。よくよく考えると Java の byte 型は符号つき整数(-128〜127)なので 255 なんて設定されるはずが無い。255 を設定すると 127 を飛び越えて -128 はじまりになり、結局255番目の位置は -1 となるのである。この -1 を2進数で表すと 1 に対する2の補数ということで 11111111 となる。そして主役の数の 255 を2進数で表すと 11111111 となる。ということは…、-1 から 255 を算出せしめるためには 11111111(2)(=-1) と 11111111(2) の論理積を求めれば 11111111(2)(=255) となるので、それでまあよろしいのかなと予想がつく。

じゃあ 128 を考えよう。2進数だと 10000000 となる。だが Java では最大値が 127 なので 127 を飛び越えて -128 として保持される。-128 を2進数で表すとこれまた 10000000 となる。んじゃ -128 から 128 を取得したい場合はどうすればよいか。今度はきちんと数式を書くとしよう。以下の通り、

  10000000(2) × x = 10000000(2)

となって、結局 x = 11111111(2) = FF(16) という解が求まる。これが結論。それじゃ上記ロジックを治療しませう。

import java.io.*;

public class ReadFile {

    public static void main(String argv[]) throws Exception {
        ReadFile rf = new ReadFile();
        rf.execute("C:/Test.txt");
    }

    public void execute(String readFile) throws Exception {
        DataInputStream in = 
               new DataInputStream(new FileInputStream(readFile));
        for(int i=0;i<7;i++){
            System.out.print(
               Integer.toString(in.readByte() & 0xFF, 16).toUpperCase());
            if(i < 6) System.out.print(",");
        }
        in.close();
    }

}

[実行結果]
FF,9A,76,97,E2,6A,19
↑うむ、景観じゃ!!
(5)Javaでファイルデータを数値に復元しよう
 
import java.io.*;

public class ReadFile {

    public static void main(String argv[]) throws Exception {
        ReadFile rf = new ReadFile();
        rf.execute("C:/Test.txt");
    }

    public void execute(String readFile) throws Exception {
        DataInputStream in = 
               new DataInputStream(new FileInputStream(readFile));
        byte data[] = new byte[7];

        for(int i=0;i<data.length;i++){
            data[i] = in.readByte();
        }
        in.close();

        //上記(2)でやった方法と同じ、せっかくだからJavaっぽい表記にしておいた
        System.out.println("Byte:" + (data[0] & 0xFF));
        System.out.println("Integer:" + 
                            ((data[1] & 0xFF) |
                             (data[2] & 0xFF) << 8)
                          );
        System.out.println("Long:" + 
                            ((data[3] & 0xFF) |
                             (data[4] & 0xFF) << 8  |
                             (data[5] & 0xFF) << 16 |
                             (data[6] & 0xFF) << 24)
                          );
    }

}

[実行結果]
Byte:255
Integer:30362
Long:426435223
↑実に良い按配!!
(6)readShort、readInt は何の値を読んだのか?
 
上記(3)では

  readShort で -25994 を取得
  readInt   で -1746769383 を取得

という不思議な結果になった。これはいったい何だったのか。結論から言うと、Java では「重み付け」の順番が異なる、ということが言えると思われる。

VB では Integer 型のデータを数値に復元する際 &H9A、&H76 と読み込み、その順に &H9A + &H76 × 256 として数値が復元される。一方 Java は VB とは逆で 0x76 × 256 + 0x9A のように復元される模様である。

readShort で -25994 と算出されたのは -0x66 × 256 + 0x76 の結果であり
readInt で -1746769383 と算出されたのは、-0x69 × 2563 +-0x1E × 2562 + 0x6A × 256 +0x19 の結果(※1)である。

※1… 値を求めると -1763546599 となり値が異なる…何故?桁落ち?ニアピン賞?
readInt() が暴走してると都合よく解釈しましょう。

まあ以下のような関数を作って読み取れば良いんじゃないでしょうか?

public short makeShort(byte p1, byte p2){
    return (short) ((p1 & 0xFF) | (p2 & 0xFF) << 8);
}
public int makeInt(byte p1, byte p2, byte p3, byte p4){
    int intValue = p1 & 0xFF;
    intValue |= (p2 & 0xFF) << 8;
    intValue |= (p3 & 0xFF) << 16;
    intValue |= (p4 & 0xFF) << 24;
    return intValue;
}


戻る