新トップページへ | Tip

JavaのTip

LastUpdate : 09/03/07

 Javaでプログラムを作る際、調べたことを書いています。

Tip
文字列をBASE64でエンコード&デコードする(javamail)。 [ 08/12/07追加 ]
文字列をBASE64でエンコード&デコードする(commons-codec)。 [ 08/12/18追加 ]
オブジェクトのシリアライズ(+深いコピー) [08/12/20追加]
ThreadLocalを使う [09/03/07追加]


文字列をBASE64でエンコード&デコードする(javamail)

 文字列をBASE64型にエンコードし、そしてそれをまたデコードします。ソースは以下のとおりです。
以下のソースで問題点・・・というか、悩ましいなぁと嘆いている部分がありますが、それは後述(w

BASE64に変換する際、ライブラリ関数を使用しています。
「 MimeUtility 」というクラスを使用しています。EclipseでMimeUtilityと入力しCtrl+spaceを押すと、以下のクラスが自動的にインポートされる様子です。

import com.sun.xml.internal.messaging.saaj.packaging.mime.internet.MimeUtility;

しかし、javamailのMimeUtilityを使うこととします。以下のアドレスからjavamailのjarをDLし、圧縮ファイルを解凍後、現れるmail.jarを、適当なところにもっていき(たとえばプロジェクトファイル内だとか・・・)、ビルドパスにmail.jarが含まれるよう設定します(Eclipseならば、「Javaのビルド・パス」のところで「jarの追加」などをやる)。
今回使用するMimeUtilityクラスは以下のものです。

import javax.mail.internet.MimeUtility;

また、javamailについては「 http://java.sun.com/products/javamail/ 」を参照してください。

以下がサンプルコードです。受け取った文字列をBase64でエンコードし、そして、それをまたデコードし、変換前、変換後の文字列が同じかどうか、調べています。

Base64Test.java
/*
 * javamail(http://java.sun.com/products/javamail/)を使用した
 * BASE64エンコード/デコードのサンプルです。
 * lib/mail.jarのライブラリを使用しています。
 */
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

//import com.sun.xml.internal.messaging.saaj.packaging.mime.internet.MimeUtility;
import javax.mail.internet.MimeUtility;

/**
 * BASE64のテスト
 * 
 * 指定可能な文字コードについては以下のサイトを参照(またまたみっけたのがこのアドレスだったw
 *      http://java.sun.com/j2se/1.3/docs/guide/intl/encoding.doc.html
 */
public class Base64Test
{       
        /** 文字をbyte配列へ変換する際の文字コードの指定(エンコードするときと、デコードするときに、同じものを使わないといけません) */
        static final String charset = "SJIS";

        /** エントリポイント */
        public static void main(String[] args)
        {
                String target="てすとですよん。";
                
                System.out.println("program start...");
                Base64Test obj = new Base64Test();
                String encodedString = obj.encodeString(target);
                String decodedString = obj.decodeString(encodedString);

                System.out.println("target : " + "[" + target + "]");
                System.out.println("encoded : " + "[" + encodedString + "]");
                System.out.println("decoded : "+ "[" + decodedString + "]");
                System.out.println("result : " + (target.equals(decodedString)?"OK":"fault..."));
        }
        
        
        /** BASE64形式で渡された文字列をエンコードします。 */
        private String encodeString(String src)
        {
                String res = null;
                OutputStream out = null;
                ByteArrayOutputStream outStream = null;
                try
                {
                        outStream = new ByteArrayOutputStream();
                        out = MimeUtility.encode(outStream, "base64");
                        out.write(src.getBytes(Base64Test.charset));
                }
                catch(Exception e)
                {
                        e.printStackTrace();
                }
                finally
                {
                        try{    if( out != null )out.close();   }catch(IOException e){  e.printStackTrace();    }
                }
                if( outStream != null )res = outStream.toString();
                return res;
        }
        
        /** BASE64形式でエンコードされた文字列をデコードします。 */
        private String decodeString(String src)
        {
                ByteArrayInputStream inputStream = null;
                InputStream in = null;
                String res = null;
                try
                {
                        inputStream = new ByteArrayInputStream(src.getBytes(Base64Test.charset));
                        in = MimeUtility.decode(inputStream, "base64");
                        
                        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                        int len;
                        byte[] buf = new byte[512];
                        while( (len=in.read(buf)) >= 0 )outputStream.write(buf,0,len);
                        
                        res = new String(outputStream.toByteArray(), Base64Test.charset);
                }
                catch(Exception e)
                {
                        e.printStackTrace();
                }
                finally
                {
                        try{    if( in != null )in.close();     }catch(IOException e){  e.printStackTrace();    }
                }
                return res;
        }
}

という感じのコードで実行結果は以下のようになります。

Windows上での実行結果
program start...
encode OK.
decode OK.
target : [てすとですよん。]
encoded : [44Gm44GZ44Go44Gn44GZ44KI44KT44CC
]
decoded : [てすとですよん。]
result : OK
■ 文字列←→Byte配列の変換の問題 ■
getBytesメソッドで文字列から、byte配列へ変換していますが、引数無しの、getBytesメソッドを使用した場合、実行環境の文字コードでbyte配列へ変換されてしまうので、注意が必要です。
上記のサンプルのように、getBytesメソッドは、文字コードを指定することができますので、文字コードを指定して、変換を行わせたほうが後々面倒はないです。

以下の例は、getBytesメソッドで、文字コードを指定しなかった場合(getBytes()で処理している)のwindowsとLinux(CentOS5.2)の実行例です。

Windows上での実行結果
program start...
encode OK.
decode OK.
target : [てすとですよん。]
encoded : [gsSCt4LGgsWCt4LmgvGBQg==
]
decoded : [てすとですよん。]
result : OK

Linux上での実行結果
[root@servlet_test tmp]# javac -classpath .:/usr/java/latest/jre/lib:/root/tmp/mail.jar Base64Test.java
[root@servlet_test tmp]# java -classpath .:/usr/java/latest/jre/lib:/root/tmp/mail.jar Base64Test
program start...
encode OK.
decode OK.
target : [てすとですよん。]
encoded : [44Gm44GZ44Go44Gn44GZ44KI44KT44CC
]
decoded : [てすとですよん。]
result : OK
[root@servlet_test tmp]#

・・・と、まぁ、エンコード結果が異なっていることが分かります。同じマシン上で動かすならば、良いのですが、エンコードした結果を他のマシンで実行させるなどした場合はまずいですので、前もって文字コードは決めておかないと、悲しい結果になります。

文字列をBASE64でエンコード&デコードする(commons-codec)。

でやったことを別ライブラリを使用してやってみます。
今回使用するのは、commons Codecというものです。詳細はhttp://commons.jakarta.jp/codec/とか[ http://commons.apache.org/codec/ ]を見てください。

Base64Test2.java
/*
 * commons-codec(http://commons.apache.org/codec/)を使用した
 * BASE64エンコード/デコードのサンプルです。
 * lib/mail.jarのライブラリを使用しています。
 */
import org.apache.commons.codec.binary.Base64;

/**
 * BASE64のテスト
 * 
 * 指定可能な文字コードについては以下のサイトを参照(またまたみっけたのがこのアドレスだったw
 *      http://java.sun.com/j2se/1.3/docs/guide/intl/encoding.doc.html
 */
public class Base64Test2 {

        /** 文字をbyte配列へ変換する際の文字コードの指定(エンコードするときと、デコードするときに、同じものを使わないといけません) */
        static final String charset = "SJIS";
        
        /** エントリポイント */
        public static void main(String[] args)
        {
                String target="てすとですよん。";
                
                System.out.println("program start...");
                Base64Test2 obj = new Base64Test2();
                String encodedString = obj.encodeString(target);
                String decodedString = obj.decodeString(encodedString);

                System.out.println("target : " + "[" + target + "]");
                System.out.println("encoded : " + "[" + encodedString + "]");
                System.out.println("decoded : "+ "[" + decodedString + "]");
                System.out.println("result : " + (target.equals(decodedString)?"OK":"fault..."));
        }
        
        
        /** BASE64形式で渡された文字列をエンコードします。 */
        private String encodeString(String src)
        {
                String res = null;
                try
                {       
                        byte[] tmp = src.getBytes(Base64Test2.charset); //文字列をバイト配列へ変換。
                        byte[] tmp2 = Base64.encodeBase64(tmp);                 //BASE64エンコード
                        res = new String(tmp2, Base64Test2.charset);    //エンコード結果を文字列に変換
                }
                catch(Exception e){     e.printStackTrace();    }
                return res;
        }
        
        /** BASE64形式でエンコードされた文字列をデコードします。 */
        private String decodeString(String src)
        {
                String res = null;
                try
                {
                        byte[] tmp = src.getBytes(Base64Test2.charset); //文字列をバイト配列へ変換。
                        byte[] tmp2 = Base64.decodeBase64(tmp);                 //BASE64デコード
                        res = new String(tmp2, Base64Test2.charset);    //デコード結果を文字列に変換
                }
                catch(Exception e){     e.printStackTrace();    }
                return res;
        }
}

Base64Test2.java実行結果
program start...
target : [てすとですよん。]
encoded : [gsSCt4LGgsWCt4LmgvGBQg==]
decoded : [てすとですよん。]
result : OK

オブジェクトのシリアライズ(+深いコピー)

オブジェクトをファイルに保存し、そのファイルを読み込み、オブジェクトが実行できるか試してみます。

シリアライズするオブジェクト
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * SerializeTestで使われる、シリアライズ対象オブジェクト
 */
public class SampleObject implements Serializable
{
        Date currentDate;
        
        /**
         * デフォルトコンストラクタ
         */
        public SampleObject()
        {
                currentDate = new Date();
        }
        
        /**
         * コンストラクタ
         * 外部からオブジェクトをうけとって、参照を保持するようにします。
         * @param d
         */
        public SampleObject(Date d)
        {
                currentDate = d;
        }
        
        /**
         * 時間の文字列を返します。
         */
        public String getCurrentDate()
        {
                SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd a HH:mm:ss.SSS");
                return format.format(currentDate);
        }
        
        /**
         * 時間をセットします。
         */
        public void setDate(Date d){    currentDate = d;        }
        
}

上記のオブジェクトをファイルに保存し、再びそれをロードし、実行してみます。

オブジェクトをシリアライズし、ファイルに保存&そのファイルをロードし、実行
/*
 * オブジェクトのシリアライズのサンプルです。
 * オブジェクトをコピーし、ファイルに保存、そのファイルを読み込み、オブジェクトとして取得し、メソッドを実行してみます。
 * 
 */
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;

public class SerializeTest
{
        public static void main(String[] args)
        {
                //ファイルに保存するオブジェクトを生成します。
                SampleObject sample = new SampleObject(new Date());
                System.out.println("object create : "+sample.getCurrentDate());
                
                //オブジェクトをファイルへ保存します。
                try
                {
                        FileOutputStream out = new FileOutputStream("sampleobject.obj");
                        ObjectOutputStream objOut = new ObjectOutputStream(out);
                        objOut.writeObject(sample);
                }
                catch(Exception e)
                {
                        e.printStackTrace();
                }
                
                //オブジェクトをファイルから読み込み、メソッドを実行します。
                try
                {
                        FileInputStream input = new FileInputStream("sampleobject.obj");
                        ObjectInputStream objInput = new ObjectInputStream(input);
                        SampleObject sampleObj = (SampleObject)objInput.readObject();
                        
                        System.out.println("resutlt       : "+sampleObj.getCurrentDate());
                }
                catch(Exception e)
                {
                        e.printStackTrace();
                }
        }

}

実行結果
object create : 2008/12/20 午後 16:13:14.292
resutlt       : 2008/12/20 午後 16:13:14.292


★おまけ

cloneメソッドを定義して、オブジェクトのコピーを通常作成しますが、子面倒です。が、以下の方法を使うと簡単にできます。
しかし、諸刃の刃でもあるので、使用には注意が必要です。

以下のコードは、いわゆる「深いコピー」というものを行います。

コピー対象のオブジェクト
/*
 * オブジェクトの深いコピーのサンプルプログラムです。
 */

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Date;

public class DeepCopyTest {

        public static void main(String[] args)
        {
                SampleObject obj1 = new SampleObject(new Date());
                
                //obj1の深いコピーを行う。
                SampleObject obj2 = (SampleObject)deepCopy(obj1);
                //obj1のフィールドの値を変更しておく(両者、同じcurrentDateを参照していないことを確認するため)
                try{    Thread.sleep(500);      }catch(Exception e){}   //0.5秒停止する。
                obj1.setDate(new Date());
                
                System.out.println("obj1 : "+obj1.getCurrentDate());
                System.out.println("obj2 : "+obj2.getCurrentDate());
        }
        
        public static Object deepCopy(Object obj)
        {
                //オブジェクトをシリアライズを利用しコピーします
                Object res = null;
                try
                {
                        ByteArrayOutputStream outByteArray = new ByteArrayOutputStream();
                        ObjectOutputStream out = new ObjectOutputStream(outByteArray);
                        out.writeObject(obj);
                        
                        ByteArrayInputStream inputByteArray = new ByteArrayInputStream(outByteArray.toByteArray());
                        ObjectInputStream input = new ObjectInputStream(inputByteArray);
                        
                        res = input.readObject();
                }
                catch(Exception e)
                {
                        e.printStackTrace();
                }
                
                return res;
        }
}

実行結果
obj1 : 2008/12/20 午後 16:16:22.518
obj2 : 2008/12/20 午後 16:16:22.005


ThreadLocalを使う

Thread内にて、そのThread固有の変数を保持したいー!って時に使用します。
ThreadLocalとはなんぞや?という場合、以下の記事がとても分かりやすいと思います。

http://www.ibm.com/developerworks/jp/java/library/j-threads3/index.html

ThreadLocalのサンプルを以下に示します(てきとーなプログラムだけど許して><)。

ThreadLocalMain.java
import java.util.HashMap;
import java.util.Map;

/**
 * 処理を実行するためのスタートポイント
 */
public class ThreadLocalMain {

        public static void main(String[] args) {
                
                System.out.println("プログラムを開始します!");
                int threadCount = 5;
                for( int i=1 ; i<threadCount+1 ; i++ ) {
                        SampleThread thread = new SampleThread(i);
                        thread.start();
                }
        }

}

/**
 * スレッドの実行クラス
 */
class SampleThread extends Thread {
        
        /** スレッドに振った番号 */
        private int threadNo = -1;
        
        /** ThreadLocal */
        private static ThreadLocal local = new ThreadLocal() {
                /** initialValueメソッドをオーバーライドし、getメソッドが返す値を初期化しています */
                protected Object initialValue() {
                        return new HashMap();
                }
        };
        
        /** コンストラクター */
        public SampleThread(int no) {
                this.threadNo = no;
        }
        
        /** スレッドのスタートポイント */
        public void run() {
                //ThreadローカルよりHashMapを取得する。
                Map map = (Map)local.get();
                map.put("MyName", "私の番号は"+threadNo+"です!");
                System.out.println(threadNo+"は値をセットしました。。。");
                try{    Thread.sleep((int)Math.random()*100);   }catch(InterruptedException e){}
                //取得したものから値をもらってみる。
                map = (Map)local.get();
                System.out.println(map.get("MyName"));
        }
}

実行結果
プログラムを開始します!
1は値をセットしました。。。
2は値をセットしました。。。
4は値をセットしました。。。
3は値をセットしました。。。
私の番号は4です!
私の番号は2です!
私の番号は3です!
私の番号は1です!
5は値をセットしました。。。
私の番号は5です!

まぁこんな感じです(検証プログラムとして、まじめに作ると大変なので、簡単につくってしまいました。。。)。
ポイントとしては、staticで宣言されているlocal変数に対し、getメソッドを呼びHashMapを取得し、全スレッド、"MyName"というキーで値を使い値をセットしていますが、出力結果は、全部自分が設定した値が帰ってきているところです。

内容のほうですが、ThreadLocalのinitialValueの返す値が、getメソッドが返すオブジェクトになる様子。
getメソッドを呼ぶ際、初期化されていなかったら、自分で初期値をセットして・・・という手間を省けます。

ThreadLocalは何にsetしたりgetしているのか・・・という話ですが、簡単に言うと、HashMapに対し、自分のスレッドIDをキーに、Objectをsetしたりgetしたりしている様子です。なので、スレッド間で共有しても問無い・・・ということらしい。