2010年12月28日火曜日

【Oracle】DataSourceを使用してデータベース接続をキャッシュ

OracleDataSourceにはプール機能(接続のキャッシュ機能)がある。Webコンテナで提供されるDataSourceのプール機能と同じもの。

この機能を使うと、Connectionの取得要求のたびにデータベースとの物理接続を確立する必要がないのでパフォーマンスが向上する。(はず)
普通は、Webコンテナの機能使えばいいのであんまり必要にならないけど、バッチ処理のようにWebコンテナ以外で実行されるアプリの場合には必須の機能だと思う。

キャッシュを使用しない場合

キャッシュを使用しない場合の実装は、【Oracle】DataSourceを使用してデータベース接続と同じ。
// DB接続前のセッション数を出力
printHogeSession("開始前");

OracleDataSource dataSource = new OracleDataSource();
dataSource.setUser("hoge");
dataSource.setPassword("hoge");
dataSource.setURL("jdbc:oracle:thin:@localhost:1521:xe");
Connection connection = dataSource.getConnection();

// getConnection呼び出し後のセッション数を出力
printHogeSession("OracleDataSource.getConnection()呼出し後");
connection.close();

// close呼び出し後のセッション数を出力
printHogeSession("Connection.close()呼出し後");

実行結果

getConnection()の呼出し後には、セッション数が1つになる。
Connection.close()を呼び出すと、物理接続がcloseされるためセッション数は0になる。

この場合、getConnectionの度にOracleとの物理接続が確立されるため、性能的に非常にまずくなる。
開始前
---------------------------------------------


OracleDataSource.getConnection()呼出し後
---------------------------------------------
HOGE             JDBC Thin Client


Connection.close()呼出し後
---------------------------------------------


キャッシュを使用した場合


以下の実装だと、OracleDataSourceが持っているキャッシュ機能を有効化できる。
ただし、OracleDataSourceのキャッシュ機能を使えるのはojdbc14.jarまで。
JDK5やJDK6用のojdbc5、ojdbc6.jarでは別の機能を使用してキャッシュ機能を有効化する必要がる。
// DB接続前のセッション数を出力
printHogeSession("開始前");

OracleDataSource dataSource = new OracleDataSource();
dataSource.setUser("hoge");
dataSource.setPassword("hoge");
dataSource.setURL("jdbc:oracle:thin:@192.168.0.5:1521:xe");

// キャッシュの設定
Properties prop = new Properties();
// 初期容量(デフォルト:0)
prop.setProperty("InitialLimit", "2");
// 最大容量(デフォルト:Integer.MAX_VALUE)
prop.setProperty("MaxLimit", "2");

// 上記で設定した、キャッシュプロパティを設定する。
dataSource.setConnectionCacheProperties(prop);
// setConnectionCachingEnabledを呼び出してキャッシュを有効化する。
dataSource.setConnectionCachingEnabled(true);

Connection connection = dataSource.getConnection();

// getConnection呼び出し後のセッション数を出力
printHogeSession("OracleDataSource.getConnection()呼出し後");
connection.close();

// close呼び出し後のセッション数を出力
printHogeSession("Connection.close()呼出し後");

実行結果

キャッシュ機能が有効になっていることが確認できる。
getConnectionを呼び出したあとでは、初期容量の物理接続が確立されている。
close呼び出し後には、キャッシュを使わなかった時とは異なり物理接続が切断されていないことが確認できる。
物理接続は切断されていなくても論理的な接続は切断されているので、close呼び出し後はDBアクセスはできない。

ちなみに、キャッシュはDataSourceのインスタンス単位にされるので、異なるインスタンスの場合には異なる接続がキャッシュされている。
開始前
---------------------------------------------


OracleDataSource.getConnection()呼出し後
---------------------------------------------
HOGE             JDBC Thin Client
HOGE             JDBC Thin Client


Connection.close()呼出し後
---------------------------------------------
HOGE             JDBC Thin Client
HOGE             JDBC Thin Client

2010年12月26日日曜日

【MySql】DataSouceを使用してデータベース接続

MySqlのJDBCドライバ(Connector/J)を使用してデータベース接続を行う方法。

DataSourceのインスタンスを生成して接続先情報を設定

// MysqlDataSourceのインスタンスを生成
MysqlDataSource dataSource = new MysqlDataSource();
// 接続先情報として、URLとユーザ、パスワードを設定
dataSource.setURL("jdbc:mysql://localhost/test");
dataSource.setUser("root");
dataSource.setPassword("password");

java.sql.Connectionの取得

Connection connection = dataSource.getConnection();

取得したConnectionを使用してSQLを実行

// JDBCのAPIを使ってSQL文(testスキーマのテーブル名をリスト表示する)を実行。
PreparedStatement statement = connection.prepareStatement(
        "select * "
        + "from INFORMATION_SCHEMA.TABLES"
        + " where table_schema = 'test'");
ResultSet set = statement.executeQuery();
while (set.next()) {
    System.out.println("set.getString(1) = " + set.getString("table_name"));
}

2010年12月24日金曜日

【ruby】シンボル

シンボルの宣言

:s = 's'
s = :s
# これは、シンボル同士の比較だから一致する。
# クォートで囲ってもシンボルは同一となる。
puts s == :"s"  # => true
puts s == :s    # => true

# これは、文字列との比較だから一致しない
puts s == "s"   # => false

string = "hoge"
# 文字列をシンボルに変換するには、internかto_symを使う
puts :hoge == string          # => false
puts :hoge == string.intern   # => true
puts :hoge == string.to_sym   # => true

シンボルを使ってリフレクション

array = Array.new
puts array                      # => []

# リフレクションでメソッドの存在チェック
puts array.respond_to?(:push)   # => true

# リフレクションでメソッド呼び出し
array.send(:push, "hoge")
puts array                      # => [hoge]

2010年12月15日水曜日

【Oracle】DataSourceを使用してデータベース接続

DataSourceを使用してOracleに接続する方法。
単純にDBに接続したいのであれば、この実装で十分可能。ただし、この実装だとgetConnection()を呼び出すたびにデータベースとの物理接続が行われて性能面に与える影響が大きくなる。
実業務では、こんなコード書くことはまずないと思う。

OracleDataSourceに接続情報を設定

この実装は、TYPE4(thin)ドライバを使用している。
URLの記述形式は、Oracleのマニュアルを参照するとよい。
// OracleDataSourceのインスタンスを生成
OracleDataSource dataSource = new OracleDataSource();
// URLを設定
dataSource.setURL("jdbc:oracle:thin:@localhost:1521:xe");
// ユーザ名、パスワードを設定
dataSource.setUser("hoge");
dataSource.setPassword("hoge");

データベース接続(java.sql.Connection)の取得

getConnection()を呼び出すことによってデータベース接続を取得する。
getConnectionは、javax.sql.DataSourceインタフェースで定義されているメソッドとなっている。
// コネクションの取得
Connection connection = dataSource.getConnection();

SQLの実行

JDBCで提供されているインタフェースを使ってSQL文を実行する。
この実装は、DBにつながったことを確認するだけなので簡単なSQL文を実行しているだけ。
// JDBCのAPIを使ってSQLを実行する。
// 実行結果として標準出力に「result = 1」と表示される。
PreparedStatement statement = connection.prepareStatement("select '1' from dual");
ResultSet resultSet = statement.executeQuery();

while (resultSet.next()) {
    System.out.println("result = " + resultSet.getString(1));
}

2010年12月4日土曜日

【Vim】検索結果のハイライト表示

下記のオプションで切り替えられる。

ハイライト表示オン

:set hlsearch
省略形は・・・
:set hls

ハイライト表示オフ

:set nohlsearch
省略形は・・・
:set nohls

現在の検索結果のハイライト表示のみオフ

:nohlsearch
省略形は・・・
:noh

2010年11月20日土曜日

【Intellij】Javaの実行

Intellijのインストールが終わったので、Javaを書いて実行してみよう。

クラスを作成するには、Project Viewでソースコードディレクトリを右クリックして、「New→Java Class」を選択する。






「Create New Class」ダイアログが表示されるので、クラス名を入力してOK。
下の画像のように、InterfaceやEnumなんかはここで選択する。Abstractはないのに、Singletonはあるのが不思議。












main関数をもつクラスを実行するには、メニューから「Run→Run」でできる。
便利な実行方法は、「F9」を押すと下の画像のようなDebug実行用のメニューが表示される。
このメニューから実行や実行時の設定(オプションの指定など)ができる。
Shiftキーを押すと、メニューがDebugからRunに変わるので通常実行する場合は、Shift+Enterでいける。


2010年11月18日木曜日

Intellij IDEAをインストール

Javaの統合開発環境の中でIntellijなるものが使いやすいらしいのでインストールしてみた。
ダウンロードは、以下のリンクからできる。最新版は、idea9で次期バージョン(idea10)は、Public Preview版がある。

http://www.jetbrains.com/idea/

インストールしたらまずは、プロジェクトを作成する。
起動すると、こんな画面が開くのでCreate New Project(Projectは、EclipseだとWorkspaceかな?)を選択















Create project from scratchを選択して次へ。(既存ソースがある場合などは、他のを選択すれば良い。)

















project file locationにプロジェクトを作りたいディレクトリを設定
Module(Eclipseのプロジェクト)をつくるので、Create moduleにもチェックを入れておく。

















ソースディレクトリを作る場合は、Create source Directoryにチェックを入れて任意のディレクトリ名を入力する。

















Javaを書くだけなので、何もせんたくせずにFinish!

















無事プロジェクトができた!!

2010年11月17日水曜日

JavaでBase64

JavaでBase64変換を作ってみた。とりあえず、エンコードのみ。
多分これで大丈夫だと思う。
もともと貼りつけていたコードがバグってたやつだったので差し替え。。

public class Base64 {

    /** エンコード用の変換表 */
    private static final char[] ENCODE = {
            'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M',
            'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
            'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm',
            'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'
    };

    public static String encode(String str) {
        // デフォルトエンコーディングでエンコード
        return encode(str.getBytes());
    }

    public static String encode(byte[] src) {
        char[] result = new char[src.length * 8];
        int pos = 0;
        for (int i = 0; i < src.length; i += 3) {
            byte byte1 = src[i];
            byte byte2 = (i + 1) < src.length ? src[i + 1] : 0x00;
            byte byte3 = (i + 2) < src.length ? src[i + 2] : 0x00;
            result[pos++] = ENCODE[((byte1 >> 2) & 0x3F)];
            result[pos++] = ENCODE[(byte1 & 0x03) << 4
                    | (byte2 >>> 4) & 0xF];
            if (i + 2 < src.length) {
                result[pos++] = ENCODE[(byte2 & 0x0F) << 2
                        | (byte3 >>> 6) & 0x03];
                result[pos++] = ENCODE[byte3 & 0x3F];
            } else if (i + 1 < src.length) {
                result[pos++] = ENCODE[(byte2 & 0x0F) << 2
                        | (byte3 >>> 6) & 0x03];
                result[pos++] = '=';
            } else {
                result[pos++] = '=';
                result[pos++] = '=';
            }
        }
        return new String(result, 0, pos);
    }
}

2010年11月3日水曜日

sqlplusのeditファイルの出力場所変更

sqlplusでeditするときに作られる「afiedt.buf」の出力先の変更方法。

sqlplusのsetコマンドで変更
set editfile /tmp/afiedt.sql

sqlplusで変更した場合、sqlplus起動するたびに設定しないといけないのでかなり面倒。常に有効にするには、glogin.sqlにコマンドを設定しておくとよい。

vi $ORACLE_HOME/sqlplus/admin/glogin.sql

末尾以下を追加
set editfile /tmp/afiedt.sql

2010年10月23日土曜日

VimでYankの情報をクリップボードと連携

VimでYankした情報をWindowsの他のアプリに貼りつけたり、他のアプリでクリップボードにコピーした情報をYankする方法。

下記のコマンドを$HOME/_gvimrc(.gvimrc)や$HOME/_vimrc(.vimrc)に記述しておく。

set clipboard=unnamed

こうすることで、Yankした情報を他のアプリで普通に貼り付け(Ctrl + c)できるし、他のアプリでコピーした情報をVimでペースト(pやP)できる。

2010年10月14日木曜日

【ruby】配列の操作

配列(array)の基本的な操作方法

要素の追加

# 末尾に"1"を追加
array.push "1"

# 末尾に"2"を追加
array >> "2"

# 任意の位置に値の挿入
array = ["2", "3"]
array.insert 1, "2"  # 2番目(0から始まるから)の要素("3")の直前に2を挿入
puts array           # ["1", "2", "3"]

array = ["1", "3"]
array.insert -1, "2" # 末尾から2番目の要素("1")の後ろに2を挿入
puts array           # ["1", "2", "3"]

# 先頭に要素を挿入
array = [1, 2, 3]
array.unshift(0)      # [0, 1, 2, 3]
array.unshift([1, 0]) # [[1, 0], 0, 1, 2, 3]
array.unshift(-1, -2) # [-1, -2, [1, 0], 0, 1, 2, 3]

要素の削除

array = [1, 2, 3, 4, 5]
array.delete(3)  # 指定された値と一致する要素が削除され、削除された値が返却される。削除後の要素は、[1, 2, 4, 5]となる。
array.delete(0)  #  削除対象が存在しないため、戻り値はnilとなる。

# 下記のようにblockを指定すると、削除対象がない場合はblockの結果が戻り値となる。
array.delete(0) {
  "'0' is not found."
}

array.delete_at(0)  # 0番目の要素を削除する。
array.delete_at(-1) # 末尾の要素を削除する。

キューやスタックみたいにあつかう

# キュー
array = []
array.push "1"  # [1]
array.push "2"  # [1, 2]
array.shift     # return 1, array = [2]
array.push "3"  # array = [2, 3]
array.shift     # return 2, array = [3]

# スタック
array = []
array.push "1"  # [1]
array.push "2"  # [1, 2]
array.pop       # return 2, array = [1]
array.push "3"  # array = [1, 3]
array.pop       # return 3, array = [1]

要素の全削除

array.clear

要素の存在チェック

array = ["1", "2", "3"]
array.include?("2") # true
array.include?("0") # false

各要素を結合

array = ["1", "2", "3"]
array = [1, 2, 3]
puts array.join       # 123
puts array.join(',')  # 1,2,3

2010年10月9日土曜日

【ruby】配列の生成と要素の参照

rubyでの配列の生成とアクセス方法

rubyの配列は、ミューブルなので生成後でも変更が可能で、さまざまなオブジェクトを代入できる。
Javaでいうところの可変配列(java.util.List)と同じような感じ。

# 配列の生成

array = [0, [0, 1], "aaa", 0..10, 0...10]
Array.new # []
Array.new(3) # [nil, nul, nul]
Array.new(3, 0) # [0, 0, 0]
Array.new(3, "a") # ["a", "a", "a"]
Array.new(3) { |i| i + i } # [0, 2, 4](中括弧の中身の計算結果が代入される。)


# 配列要素へのアクセス
array = [0, [0, 1], "aaa", 0..10, 0...10]
puts array[0] # 0
puts array[2] # "aaa"
puts array[100] # 要素が存在しないからnil

# マイナスを指定すると末尾からのアクセスになる。
# -1=末尾の要素、-2=末尾から2番目の要素
puts array[-1] # 0...10
puts array[-2] # 0..10

sqlplusでBLOBデータの参照

BLOBデータ型は、通常のsql実行では参照することができません。なのでわざわざ、BLOBの項目をselect句の取得項目から削除したりしてます。

どうしてもお手軽にsqlplusからblobのデータを参照したいことがあると思います。
そんなときは、デフォルトで提供されているパッケージを使うとそれなりに表示することができます。

テーブル定義

15:56:46 SQL> desc blob_test
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 ID                                        NOT NULL CHAR(2)
 BLOB_DATA                                          BLOB

BLOBカラムを含めてselectを実行

SQL> select * from blob_test;
SP2-0678: Column or attribute type can not be displayed by SQL*Plus

blob型カラムがあるのでエラーになります。
日本語メッセージだとこんな感じになります。
SP2-0678: 列または属性型はSQL*Plusでは表示できません。

最近知ったけど、Oracle11gのsqlplusだとこの問題が解決されてる。
こんな感じに16進数でダンプされるんだよね。やるなOracle。
16:20:37 SQL> select * from blob_test;

ID
--
BLOB_DATA
----------------------------------------------------------------------------------------------------
01
333435

02
44462D312E340D25E2E3CFD30D0A32303037352030206F626A0D3C3C2F4C696E656172697A656420312F4C20313030393131
35342F4F2032303037382F4520313439353439322F4E20313330302F5420

また、sqlplusコマンドのlobofを使うとダンプ開始位置を任意に変えられるようになってます。
マニュアルは、こちら。Oracleマニュアル
set lobof 100

sqlplusでBLOBを参照する方法

Oracle10gのクライアントの場合は、下記の方法でBLOBデータを参照する必要がある。

DBMS_LOBパッケージ

dbms_lobパッケージのsubstr関数を使うことによって、16進数でダンプできます。
ただし、上限は2000バイト(raw型の上限にひっかかるのかな?)なので、でかいサイズだと分割しないといけなかったりします。

この例だと、一括で前バイト数をだんぷしていますが、
dbms_lob.substr(カラム名, 読み込むバイト数, 読み込み開始バイト位置)
としてあげると任意のバイトデータをダンプできます。

16:30:25 SQL> r
  1* select dbms_lob.substr(blob_data, dbms_lob.getlength(blob_data)) from blob_test where id = '01'

DBMS_LOB.SUBSTR(BLOB_DATA,DBMS_LOB.GETLENGTH(BLOB_DATA))
----------------------------------------------------------------------------------------------------
3132333435

utl_rawパッケージ

utl_rawパッケージを使うと、文字列などに変換して出力できます。
dbms_lobと同じように上限は2000バイトみたいです。

0x3132333435をダンプしてるので、12345となります。
16:35:41 SQL> select utl_raw.cast_to_varchar2(blob_data) from blob_test where id = '01';

UTL_RAW.CAST_TO_VARCHAR2(BLOB_DATA)
----------------------------------------------------------------------------------------------------
12345

2010年10月6日水曜日

IME切り替えキーの変更(Windows7)

IME切り替えキーの変更で書いた内容のWindows7版

  • 言語バーの設定画面を開く
  • Microsoft IMEのプロパティボタンをクリックしてIMEのプロパティ画面へ
  • 編集操作タブへ移動
  • キー設定の変更ボタンをクリックして、IME詳細プロパティ画面を開く
  • IME切り替えを割り当てたいキーの「入力/変換済み文字なし」欄をダブルクリック
  • 機能選択画面で「IME-オン/オフ」を選んでOKをおす


2010年9月28日火曜日

【vimプラグイン】ファイルオープン履歴を管理

vimで開いたファイルの履歴を管理してくれます。

ダウンロードサイト
http://www.vim.org/scripts/script.php?script_id=521

履歴の表示方法
MRUコマンド(:MRU)で履歴を表示することができます。
履歴からファイルを開くには、カーソルをあててEnterまたは、o(新しい窓で開く)を押下します。


その他設定

  • 履歴の最大数
デフォルトは100となっているので、任意の値に変更ができる。さすがに100はおおい。
let MRU_Max_Entries = 50

  • 履歴からファイルを開いた際の動作設定
'1'を設定すると、自動で履歴窓を閉じてくれる。デフォルトは'0'になっている。
let MRU_Auto_Close = 1

vimを最大化で起動

vimってデフォルトの起動サイズが微妙なサイズなのがちょっと・・・。
コーディングとかする場合ってやっぱり最大化できどうしてほしいんだよね。

てことで、vimを最大化で起動する方法(Windowsのgvimのみ有効)

WindowsユーザのHOMEディレクトリの「_gvimrc」(vimで、「:e $HOME/_gvimrc」で開ける)に下記行を追加する。

au GUIEnter * simalt ~x


Windowsの場合は、ランチャーとかでvimを起動している人も多いと思うけどそんな場合は、
ランチャー側で起動時のサイズを指定できたりするのでそれでもいいかも。

2010年9月24日金曜日

IME切り替えキーの変更

IME切り替えキーが「半角/全角」キーで遠いので変更してみた。



  • 言語バーの設定画面を開く
  • プロパティボタンをクリックしてIMEのプロパティ画面を開く

  • 設定ボタンをクリックしてIME詳細プロパティ画面を開く
  • IME切り替えを割り当てたいキーの「入力/変換済み文字なし」欄をダブルクリック
  • 機能選択画面で「IME-オン/オフ」を選んでOKをおす
この画像で表示されてる設定だと、変換キーにIME切り替えを割り当ててます。
再変換なんて使わないから、このキーを割り当てるのが個人的にはいい感じ。




2010年2月25日木曜日

Vimの画面分割

Vimの画面分割を使うはめっちゃ便利です。画面分割しても、基本キーボードのみで画面間を移動できるし。
特に大きいサイズのディスプレイ使ってると画面を分割しまくって関連するファイルや、同一ファイルのほかの部分を参照しながら開発できるので、かなり生産性アップの予感です。
単なるたて分割だと、下の画像みたいになるけどアクティブな画面で分割を行うとさらに分割されるよ。

●分割するとこんな感じ

















●画面分割のコマンド
操作内容コマンド
縦分割Ctrl-w + v
横分割Ctrl-w + v
分割サイズを均等にCtrl-w + =
右の画面へ移動Ctrl-w + l
下の画面へ移動Ctrl-w + j
左の画面へ移動Ctrl-w + h
上の画面へ移動Ctrl-w + k
アクティブな画面を閉じる Ctrl-w + c
ほかの画面を閉じる Ctrl-w + o

2010年2月20日土曜日

パック数値とは?


最近パック数値なるものを扱う機会があったので、忘れないようにメモ。

パック数値とは?

  • 1バイトの数字を4ビットで表現する数値のこと。ゾーン数値と比べて単純計算ではあるが、使用容量が半分ですむ。
  • パック数値には、符号なしパックと符号ありパックが存在する。
  • 符号のあらわし方は、末尾の4ビットで表現する。符号なしの場合でも、末尾4ビットで符号なしであることを設定する必要あり。
  • 末尾4ビットが必ず符号領域となるため、ゾーン数値で表した場合の桁数は奇数となる。

【ゾーン数値とパック数値の比較】

10進数数値 ゾーン数値(JISの場合) 符号なしパック 符号ありパック
12345 0x3132333435
(5バイト)
0x12345F
(3バイト)
0x12345C
(3バイト)
+12345 0x31323334C5
(5バイト)
- 0x12345C
(3バイト)
-12345 0x31323334D5
(5バイト)
- 0x12345D
(3バイト)
上記表でも分かるとおり、符合なしは0xFで表現され、プラスは0xC、マイナスは0xDで表現される。

パック数値をアンパックするソース

Javaの場合

// 呼び出し例
// unpack(new byte[]{0x01, 0x2F});  -> 12
// unpack(new byte[]{0x00, 0x00, 0x33, 0x44, 0x0D}); -> -33440
public static long unpack(byte[] pack) {
    long num = 0;
    int weight = 1;
    for (int i = pack.length - 1; i >= 0; i--) {
        num += (pack[i] >> 4) * weight;
        weight *= 10;
        if (i != 0) {
            num += (pack[i - 1] & 0x0F) * weight;
            weight *= 10;
        }
    }
    byte sign= (byte) (pack[pack.length - 1] & 0x0F);
    if (sign == 0x0F || sign == 0x0C) {
        // nop
    } else if (sign == 0x0D) {
        num *= -1;
    } else {
        throw new IllegalArgumentException("invalid sign");
    }
    return num;
}

ruby

def self.un_pack(pack)
  sprintf("%X", pack).gsub(/^(.+)(.)$/) {|str|
    case $2
      when "F", "C"
        ""
      when "D"
        "-"
      else
        raise RuntimeError.new("illegal sign. sign = [#{$2}]")
    end + $1
  }.to_i
end

2010年2月7日日曜日

ruby1.9で文字コード変換

Unicodeに対応したruby1.9で文字コードを変換してみる。
encodeメソッドを使うことによって文字コードを変換できるらしい。
encode(to_encoding, from_encoding)ってな感じで使う。

サンプルコード

#coding:utf-8
# UTF-8
utf8_str = "あいうえお"
puts utf8_str.encoding # => UTF-8

# UTF-8→SJIS
sjis_str = utf8_str.encode("sjis", "utf-8")
puts sjis_str.encoding # => SJIS

実行結果

UTF-8
Shift_JIS

ファイル入出力時のエンコーディングはどう指定するんだろう。Javaだと明示的にしていしないと、
OSのデフォルトエンコーディングで入出力されるんだが。こんど調べてみるか。

【Oracle】JDBC経由でBLOBデータ取得

JDBC経由でBLOBデータを更新する場合は、JCBC経由でBLOBデータ挿入を参照。

JDBC経由でBLOBデータを参照するにはいくつかの方法があると思うので、パターンごとに実装方法をまとめてみた。

取得されるサイズが少量の場合

データベース上のBLOBのサイズが小さい場合は、ResultSet#getBytesを使って一括でメモリ上に展開するのが良いと思う。
実際にのコードは、こんな感じになる。
PreparedStatement statement = con.prepareStatement( 
        "select blob_data FROM blob_test where id = ?"); 
try { 
    statement.setString(1, "01"); 
    ResultSet set = statement.executeQuery(); 
    if (set.next()) { 
        // ResultSet#getBytesを呼び出して、BLOBのデータを取得する。 
        byte[] bytes = set.getBytes(1); 
    } 
} finally { 
    statement.close(); 
}


取得されるサイズが大きい場合

取得されるBLOBのサイズが大きくなる場合(例えばファイルをBLOBのカラムに格納していた場合)は、メモリ上にすべてのデータを展開するとヒープを圧迫するのでよろしくない。
このような場合は、一定サイズを読み込みつつファイルなどに落としてあげるのが良い。
コードは、こんな感じ。Blobから取得したInputStreamからデータを読み込んでファイルなどに出力してあげれば良い。
PreparedStatement statement = con.prepareStatement( 
        "select blob_data FROM blob_test where id = ?"); 
try { 
    statement.setString(1, "01"); 
    ResultSet set = statement.executeQuery(); 
    if (set.next()) { 
        // getBlobを呼び出し、Blobオブジェクトを取得する。 
        Blob blob = set.getBlob(1); 

        // BlobオブジェクトからInputStreamを取得 
        InputStream stream = blob.getBinaryStream(); 
        printFile(stream); 
    } 
} finally { 
    statement.close(); 
}

2010年1月31日日曜日

【Oracle】JDBC経由でBLOBデータ挿入

BLOB型のカラムにJDBC経由でデータを挿入する方法。
Oracle11gのマニュアルには、BLOBよりも直接バインディングを使うとラウンドトリップを削減できるとある。
また、直接バインディングではバッチ実行を使った場合でも、1回のネットワーク操作で送信されるとあるので、もはやBLOBオブジェクトって使うことないのではないかと。(Integer#MAX_VALUEを超えるデータ量を、設定する場合はBLOBを使うしかないけど、そんなデータ量になることなんてまれな気もするし。)

直接バインディングの実装例
この実装でも、バインドされる値が4000バイトを超えると(4001バイト以上)ストリームバインディングになるらしい。
PreparedStatement statement = con.prepareStatement(
        "insert into blob_test (ID, BLOB_DATA) values (?, ?)");
try {
    statement.setString(1, "01");
    // BLOBに10進数の「12345」を設定
    statement.setBytes(2, new byte[] {0x31, 0x32, 0x33, 0x34, 0x35});
    statement.executeUpdate();
} finally {
    statement.close();
}

実行結果
insert文でバインドした値(0x31, 0x32, 0x33, 0x34, 0x35)が挿入されている。





ファイルなどのデータサイズが大きい物を登録する方法。
ファイルの場合であれば、ヒープ上にファイルの中身を読み込んでからバインドするのではなくInputStreamをバインドしてあげればよい。
この場合は、データサイズが4000バイトを超えるのでストリームバインディングになるらしい。またラウンドトリップが複数回になるので、それなりに性能面に与える影響があると思う。
PreparedStatement statement = con.prepareStatement(
        "insert into blob_test (ID, BLOB_DATA) values (?, ?)");
FileInputStream fis = null;
try {

    statement.setString(1, "02");
    // BLOBにFileInputStreamを設定
    File file = new File("C:\\work\\hoge.pdf");
    fis = new FileInputStream(file);
    statement.setBinaryStream(2, fis, (int) file.length());
    statement.executeUpdate();
} catch (FileNotFoundException e) {
    throw new RuntimeException("file io error.", e);
} finally {
    if (fis != null) {
        try {
            fis.close();
        } catch (IOException e) {
            // nop
        }
    }
    statement.close();
}

実行結果
でかめのPDFをinsertしてみたが、ちゃんとDBに登録されている。

2010年1月17日日曜日

VBAでの配列

VBAの配列は、JavaやCとは異なり要素数を指定するのではなく、添え字の最大数を指定するようになっている。なので、配列を0から始めた場合は0~最大数となりJavaやCの配列より要素数が1大きくなるのである。このことをきちんと理解していないと、思わぬ不具合が埋め込んでしまう可能性があるので注意が必要である。

個人的には、「Option Base」ステートメントを使用して配列の最小値を1に変更してあげて、添え字の最大数と要素数をイコールにしてあげることが好ましいと思う。


2010年1月16日土曜日

インスタンス生成のコスト

Javaでのインスタンス生成時のコストはかなり高いといわれている。このため、繰り返し処理(特にバッチ処理かな?)では、インスタンス生成の回数を減らすのが好ましいとのことである。

本当にそこまで気にしなければいけないのかを計測してみました。

・計測内容
JDK5とJDK6で下記処理を50万回行った際の処理時間を計測。
  1. List#newとList#clear
  2. StringBuffer#newとStringBuffer#setLength
  3. StringBuilde#newとStringBuilder#setLength

・計測結果
計測内容
JDK5
JDK6
List:new
3,422ms
3,032ms
List:clear
3,391ms
2,906ms
StringBuffer:new
4,375ms
3,750ms
StringBuffer:setLength
3,687ms
3,297ms
StringBuilder:new
4,000ms
3,421ms
StringBuilder:setLength
3,750ms
3,141ms

・結果
やはり、インスタンスを生成しなおすよりはArrayList#clearやStringBuffer#setLengthを使ったほうが早いことが分かりますね。
また、JDK6では処理の最適化が行われているのか全体的に性能は改善しています。ただし、インスタンス生成をした場合としなかった場合の、性能差はあまり変わっていません。

・どう対応すべきか
計測結果を見ると、明らかにインスタンス生成のコストがオーバヘッドになっていることが分かります。ただし、だからといって何が何でもインスタンス生成を控えるべきかといったら??な気がします。
実システムでは可読性や保守性も意識して作成しなければならないため、インスタンス生成のコストを削減することによって、それらを犠牲にすることはNGであるはずです。たとえば、参照渡しを多用していて値の書き変わる場所やタイミングが不明瞭になったり、クラス間の結合度が高くなったりするのはよろしくないですよね。
ただし、限られたスコープ内で閉じられている のであれば、積極的にコスト削減を行っていくべきですよ。
まぁ、インスタンス作成のコスト削減と可読性・保守性のトレードオフを見極めて適宜判断していくしかないのかなっと。