技術メモ

神奈川在住のITエンジニアの備忘録。おもにプログラミングやネットワーク技術について、学んだことを自分の中で整理するためにゆるゆると書いています。ちゃんと検証できていない部分もあるのでご参考程度となりますが、誰かのお役に立てれば幸いです。

16進ダンプ文字列からバイナリ列を作成するスクリプト

以前、ファイルの中身を16進ダンプするperlスクリプトを作成した。
akrad.hatenablog.com

上記に対して、今回作成したperlスクリプト makeBinFromHex.pl は、この逆を行うものである。つまり、16進ダンプ文字列からバイナリ文字列を作成する。例えば、入力ファイルに「616263」と書かれていたら、出力ファイルには「abc」が出る。
使い方は簡単で「perl makeBinFromHex.pl "ファイル"」と実行すると、ファイルの中身をバイナリ文字列にしたもの (packしたもの) を "packed_ファイル" に出力する。

makeBinFromHex.pl

use strict;
use warnings;

my $inFile = $ARGV[0];
if (!-f $inFile) {
    die "There is not $inFile file...";
}
my $outFile = "packed_" . $inFile;

open(my $inFh,  "< $inFile")  or die("Error :$!");
open(my $outFh, "> $outFile") or die("Error :$!");

while (my $line = <$inFh>) {
    chomp($line);
    my $binData = pack("H*", $line);
    print($outFh $binData);
}

close($inFh);
close($outFh);

Javaの内部の文字コード

Javaの内部の文字コードUTF-16 である。
http://www.atmarkit.co.jp/ait/articles/0706/21/news129_2.html


なので、Javaのプログラムが外部から読み込んだ SJISUTF-8などの文字列は、JavaのStringオブジェクトになる際に (内部コードである) UTF-16 に変換される。

例えば、以下のサンプルプログラムの通り、(外部文字列に見立てた)「あいう」という文字列に対して、Windows-31J としてバイト列を作成したもの(bytesWin31J)と、UTF-8 としてバイト列を作成したもの(bytesUTF8)は、当然であるが異なっている。
一方で、各々をもとに生成したStringオブジェクト(stringFromWin31J, stringFromUTF8)については、ともに中身の文字列が UTF-16 に変換されているため、同じものになっている。

import java.io.UnsupportedEncodingException;

public class testInternalCode {

    public static void main(String[] args) {
        String str = "あいう";
        try {
            byte[] bytesWin31J = str.getBytes("Windows-31J");
            byte[] bytesUTF8   = str.getBytes("UTF-8");

            dumpHexString(bytesWin31J);  // 82 a0 82 a2 82 a4 
            dumpHexString(bytesUTF8);    // e3 81 82 e3 81 84 e3 81 86 

            String stringFromWin31J = new String(bytesWin31J, "Windows-31J");
            String stringFromUTF8   = new String(bytesUTF8,   "UTF-8");

            if (stringFromWin31J.equals(stringFromUTF8)) { // True
                System.out.println("stringFromWin31J is same as stringFromUTF8");
            }

            for (char c : stringFromWin31J.toCharArray()) {
                System.out.print(Integer.toHexString(c) + " "); // 3042 3044 3046 (UTF16での "あいう")
            }
            System.out.println("");

            for (char c : stringFromUTF8.toCharArray()) {
                System.out.print(Integer.toHexString(c) + " "); // 3042 3044 3046 (UTF16での "あいう")
            }
            System.out.println("");

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

    private static void dumpHexString(byte[] bytes) {
        for (byte b : bytes) {
            System.out.printf("%02x ", b);
        }
        System.out.println();
    }
}

Javaでスレッドを作成する時の注意点

まず、Javaでスレッドを作成する基本的な方法は、以下の2つである。

  • Threadクラスを継承したクラスのオブジェクトを生成して、そのオブジェクトのstart()メソッドを実行する。
  • Runnableインタフェースを実装したクラスのオブジェクトを生成し、それを引数としてThreadクラスのオブジェクトを生成し、そのThreadクラスのオブジェクトのstart()メソッドを実行する。

後者の方法では、Threadクラスのオブジェクトを生成して、それ経由で実施したい処理のスレッドを実行する必要がある。そうしないで、Runnableインタフェースを実装したクラスのオブジェクトのrun()メソッドを直接呼び出してしまうと、マルチスレッド(並行処理)にならない。

つまり、以下のように書いてしまうと、並行処理にならない。
# r1 の処理が全部終わってから、r2 が実行されてしまう。

public class ThreadTest {
  public static void main(String[] args) {
    System.out.println("Start!");
    
    Runnable r1 = new Runnable() {
      public void run() {
        try {
          for (int i=0; i<100; i++) {
            System.out.println("Runnable1" + ": " + i);
            Thread.sleep(100);
          }
        } catch (Exception ex){
          System.out.println(ex.getStackTrace());
        }
      }
    };
      
    Runnable r2 = new Runnable() {
      public void run() {
        try {
          for (int i=0; i<100; i++) {
            System.out.println("Runnable2" + ": " + i);
            Thread.sleep(100);
          }
        } catch (InterruptedException ex) {
          System.out.println(ex.getStackTrace());
        }
      }
    };

    //-----------------------------------------------
    // After r1 finished completely, r2 is executed.
    //-----------------------------------------------
    r1.run();
    r2.run();
  }
}


並行処理にしたいならば、

r1.run();
r2.run();

の箇所を、上述の方法に従って、

Thread thr1 = new Thread(r1);
Thread thr2 = new Thread(r2);
thr1.start();
thr2.start();

と書く必要がある。

AutoMDI/MDI-X

以前ネットワークを構築した時、ネットワーク機器同士をLANケーブルで接続する時はクロスケーブルで、ネットワーク機器と端末機器を接続する時はストレートケーブルで接続していた。

これは、LANポートに以下の2種類があり、

  • MDI (Medium Dependent Interface) 型:コンピューターなどの端末機器で使用される。
  • MDI-X (Medium Dependent Interface Crossover) 型:ネットワーク機器で使用される。

MDI 同士や MDI-X 同士を繋ぐときはクロスケーブルで、MDI と MDI-X を繋ぐときはストレートケーブルで接続する必要があるからである。

 

ただ最近では、「Auto MDI/MDI-X」という、通信相手のLANポートの状態を見て自動的に MDI と MDI-X を切り替える機能を搭載したネットワーク機器が一般的になっており、接続する際に LANケーブルの種類 (クロス/ストレート) を気にする必要はなくなってきている。便利な世の中になってきましたね。

Javaのスタックトレースを解析する時のコツ

フレームワークなどを多用した大規模なJavaプログラムにおいて、エラーが起きてログにスタックトレースが出ると、その量が膨大過ぎてどうやって解析すれば良いのか困ることがある。そんな時に自分が心掛けていることを書く。

まず、アプリケーション独自の部分を見る

通常、スタックトレースには、フレームワークのコードの部分とアプリケーション独自のコードの部分が入り混じる。問題を引き起こしているのは、フレームワークよりアプリケーション独自の部分であることの方が多いので、まずはアプリケーション独自の部分を見て、その部分のコードを解析するのが良いと思う。それで、アプリケーション独自の部分に問題がなければ、フレームワークの方を疑う。

一番下の「Caused by」ブロックの一番上のメソッドのコードを見る

スタックトレースが「Caused by ・・・」で多段になっていたら、一番下の「Caused by」ブロックの一番上のメソッドが大元の例外の発生源なので、そのソースコードを見ると何か分かるかも。

例えば、スタックトレース

ExceptionA
    at method3
    at method2
    at method1
Caused by: ExceptionB
    at method5
    at method4
    at method3
Caused by: ExceptionC
    at method8
    at method7
    at method6
    at method5

となっていたら、大元の例外の発生源は method8 なので、その処理内容を見れば何か分かるかもしれない。なお、直近の例外の発生源であるmethod3 を見ると有効な時もある。


ちなみに、上記についての簡単なサンプルプログラムと、その実行結果を以下に示す。

public class ExceptionChainSample {

    public static void main(String[] args) {
        new ExceptionChainSample().exec();
    }

    private void exec() {
        try {
            method1();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
    private void method1()  throws Exception {
        method2();
    }
    private void method2()  throws Exception {
        method3();
    }
    private void method3() throws Exception {
        try {
            method4();
        } catch (Exception ex) {
            throw new TransformedException(ex);
        }
    }
    private void method4() throws Exception {
        method5();
    }
    private void method5() throws Exception {
        try {
            method6();
        } catch (Exception ex) {
            throw new TransformedException(ex);
        }
    }
    private void method6() throws Exception {
        method7();
    }
    private void method7() throws Exception {
        method8();
    }
    private void method8() throws Exception {
        throw new OrgException("test exception");
    }

    private class OrgException extends Exception {
        OrgException(String msg) {
            super(msg);
        }
    }
    private class TransformedException extends Exception {
        TransformedException(Throwable th) {
            super(th);
        }
    }
}


実行結果は以下。

ExceptionChainSample$TransformedException: ExceptionChainSample$TransformedException: ExceptionChainSample$OrgException: test exception
	at ExceptionChainSample.method3(ExceptionChainSample.java:24)
	at ExceptionChainSample.method2(ExceptionChainSample.java:18)
	at ExceptionChainSample.method1(ExceptionChainSample.java:15)
	at ExceptionChainSample.exec(ExceptionChainSample.java:9)
	at ExceptionChainSample.main(ExceptionChainSample.java:4)
Caused by: ExceptionChainSample$TransformedException: ExceptionChainSample$OrgException: test exception
	at ExceptionChainSample.method5(ExceptionChainSample.java:34)
	at ExceptionChainSample.method4(ExceptionChainSample.java:28)
	at ExceptionChainSample.method3(ExceptionChainSample.java:22)
	... 4 more
Caused by: ExceptionChainSample$OrgException: test exception
	at ExceptionChainSample.method8(ExceptionChainSample.java:44)
	at ExceptionChainSample.method7(ExceptionChainSample.java:41)
	at ExceptionChainSample.method6(ExceptionChainSample.java:38)
	at ExceptionChainSample.method5(ExceptionChainSample.java:32)
	... 6 more


エラーが起きて膨大なスタックトレースが出力されると途方に暮れそうになるが、ここに書いたようなテクニックを駆使しつつ、何とか対応していきたい。

ファイルの中身を16進ダンプするスクリプトをちょっとエンハンス

以前、ファイルの中身を16進ダンプで出力するperlスクリプト dumpHexStr.pl を紹介した。
akrad.hatenablog.com

ところが、これではファイル内に改行があった場合にそれが省かれてしまうので、改行も含めて16進ダンプするようにした。

use strict;
use warnings;

my $inFile = $ARGV[0];
if (!-f $inFile) {
    die "There is not $inFile file...";
}
my $outFile = "dumped_" . $inFile;

open(my $inFh,  "< $inFile")  or die("Error :$!");
open(my $outFh, "> $outFile") or die("Error :$!");
binmode($inFh);

while (my $line = <$inFh>) {
    my $hexDump = unpack("H*", $line);
    print($outFh $hexDump)
}

close($inFh);
close($outFh);

使い方は、以前のものと同じ。


なお、

binmode($inFh);

としているのは、ファイルから読み込んだ改行コードをそのまま扱うため。
# Windows上で実機検証したところ、binmode しない状態だと、読み込んだ CRLF(0d0a) が LF(0a) に変換されてしまった。

binmodeしないと、perl は ascii mode で改行を読む込むわけだが、perl は元々UNIX上で動くプログラムなので、UNIXのデフォルトの改行コードである LF(0a) に勝手に変換されるのではないか、と推測している。

メソッド間で変数をやり取りする方法

Javaでメソッド間で変数をやり取りする方法について書く。まずは一覧。 

  • 引数で渡す
  • メンバ変数に格納して渡す
  • static変数に格納して渡す
  • シングルトンなクラスのオブジェクトに格納して渡す

以下、それぞれの使いどころと注意点について、少し詳しく説明する。

 

引数で渡す

 メソッドが呼び出しの階層関係にある場合は、これがシンプルで使いやすい。ただ、何でもかんでも引数で渡すようにすると、メソッドの引数の数が多くなってしまうので注意。

 

メンバ変数に格納して渡す

クラス内の多数のメソッドから使用される変数の場合、いちいち引数で渡すのは面倒なので、メンバ変数に格納してそこから使うのが良いと思う。メンバ変数はクラス内のグローバル変数だと考えれば良い。なので、マルチスレッドプログラムで、そのメンバ変数を変更する処理が複数同時に走る可能性がある場合は、排他処理を実装しないといけない。

 

static変数に格納して渡す

static変数はオブジェクトではなくクラス側が持っている変数であり、プロセス内で一意のものである。つまり(C言語における)グローバル変数である。ただ、static変数はfinal指定子を付けて定数として扱われることがほとんどなので、メソッド間の変数のやり取りという観点では、使われることはまあないだろう。

 

シングルトンなクラスのオブジェクトに格納して渡す

staticな変数と同様、プロセス内で一意のもので、グローバル変数(オブジェクト)である。こちらもstaticな変数と同様、メソッド間の変数の受け渡しという観点ではあまり使われない。これはJavaでは基本的にはグローバル変数を使わない方針であることによるだろう。

 

他にもあるかもしれないが、とりあえず自分が知っているものを挙げてみた。