2010年4月22日木曜日

Effective javaまとめ4 重複したオブジェクトを生成するのを避ける2

前回の例は、対象となるオブジェクトが初期化後に変更されないため、再利用できることは明白でした。
今回は、アダプターの場合について考えます。

アダプターとは、内部で保持しているオブジェクトに対する代替のインターフェースを提供しながら、その内部で保持しているオブジェクトへの委譲を行っているオブジェクトです。

アダプターは内部で保持しているオブジェクトの状態以外の状態をもっていないため、ある特定のオブジェクトに対するアダプターのインスタンスを2つ以上生成する必要がありません。

MapインターフェースのkeySetメソッドは、そのマップ内の全てのキーから構成されているMapオブジェクトのsetビューを返します。

keySetメソッドの返すsetビューは、なんど呼び出さいても同一のインスタンスを返して問題ありません。
なぜなら、setビューが内包しているMapオブジェクトは、同一のインスタンスであるべきだからです。
setビュー経由で操作したMapオブジェクトの変更は、全てのsetビューで共有されるべきです。

以下はHashMapのkeySetメソッドの実装です。
------------------------------------------------------------

public Set keySet() {
Set ks = keySet;
return (ks != null ? ks : (keySet = new KeySet()));
}

------------------------------------------------------------
HashMapのフィールド変数であるkeySetが初期化されていない場合のみKeySetのインスタンス化を行っています。

本項目は、オブジェクトの生成はコストがかかるため避けるべきである、ということを意味しているわけではありません。
コンストラクタで明示的な処理をしていないオブジェクトの生成はほとんどコストがかかりません。(家マシンでは100万回インスタンス化して、26ms程度)また、オブジェクトをつかいまわすことで、思わぬバグやセキュリティホールを埋め込む可能性があります。また、プログラムの保守性、可読性も低下する可能性があります。

明らかにコストがかかるオブジェクト(DBコネクション生成等)や、複数のオブジェクトを生成することに全く意味が無い場合に、オブジェクトを使いまわすべきです。

--> 終わり

2010年4月17日土曜日

Effective javaまとめ4 重複したオブジェクトを生成するのを避ける1

過去のブログを読み返してましたが、日本語が崩壊しているところが多々ありますね・・。
直そうかと思いましたが、めんどーくさいので放置します。ごめんなさい。

---------------------------------------
項目4 重複したオブジェクトを生成するのを避ける

機能的に同じオブジェクトが必要となる都度、新たに生成する代わりに、1つのオブジェクトを再利用することが、大抵の場合適切です。オブジェクトが不変(状態を変更することができない)であれば常に再利用できます。

不変オブジェクトの代表とも言えるStringを利用した以下の例を見てみます。

---------------------------------------
【パターン1】

String s = new String("kows");

---------------------------------------

これは、実行される度に新たなオブジェクトが生成されます。
これを、以下のように変更します。

---------------------------------------
【パターン2】

String s = "kows";

---------------------------------------

これは、実行される度に新たなオブジェクトを生成せず、同一のStringオブジェクトを使いまわします。どちらのコードが効率が良いでしょうか。

---------------------------------------
【パターン1を100万回繰り返す:重複オブジェクトを生成する】

public static void main(String[] args) {
long start = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
String s = new String("mackagy");
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}

結果:409

【パターン2を100万回繰り返す:オブジェクトを使いまわす】

public static void main(String[] args) {
long start = System.currentTimeMillis();
for(int i = 0; i < 1000000; i++) {
String s = "mackagy";
}
long end = System.currentTimeMillis();
System.out.println(end - start);
}

結果:40

---------------------------------------

オブジェクトを使いまわす方が効率が良いみたいです。

※超余談ですが、パターン1を String s = new String() で実行すると、一番遅いです。
String()は、内部で更にchar配列をnewするためです。

不変クラスにはだいたいstaticファクトリーメソッドが提供されています。自分でクラスを設計する場合も提供するべきです。コンストラクタは呼び出される度に新たなオブジェクトを生成しますが、staticファクトリーメソッドは新たなオブジェクトを生成しません。

不変オブジェクトを再利用する以外にも、決して修正されることが分かっている可変オブジェクトを再利用することもできます。

以下の例を見てください
----------------------------------------
【パターン1】

public class Person {
private final Date birthDate;

public Person (Date birthDate) {
this.birthDate = birthDate;
}

public boolean bornAfter2000() {
Calendar base = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
base.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
Date baseDate = base.getTime();
return baseDate.compareTo(birthDate) >= 0;
}

}
----------------------------------------
bornAfter2000メソッド(役割がかなり変ですがあんまり気にしないでください)は呼び出されるごとに、Calendar、Dateオブジェクトを生成します。しかし、これは毎回生成しなくても良いはずです。(必ず2000年1月1日固定となっているため)

これは、以下のように変更することができます。
----------------------------------------
【パターン2】

public class Person {
private final Date birthDate;
private static Date baseDate;

static {
Calendar base = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
base.set(2000, Calendar.JANUARY, 1, 0, 0, 0);
baseDate = base.getTime();
}

public Person (Date birthDate) {
this.birthDate = birthDate;
}

public boolean bornAfter2000() {
return birthDate.compareTo(baseDate) >= 0;
}
}
----------------------------------------
staticイニシャライザは、クラスがロードされた時点で一度だけ実行されるため、不必要にCalendar, Dateオブジェクトが生成されなくなります。

それぞれ100万回実行すると、パターン1は2912ミリ秒かかりますが、パターン2はたった19ミリ秒です。かなり効果があるといえると思います。

ただし、パターン1ではbornAfter2000が呼ばれない限りCalendar,Dateのインスタンスが生成されることはありませんでしたが、パターン2では必ずbornAfter2000を呼び出さなくても、必ずインスタンスが生成されることになります。このへんはちょっと注意が必要です。

---> 続く

2010年4月12日月曜日

Effective javaまとめ3 インスタンス化不可能を強制

FFくりーちゃーず2が6末に発売。待ちきれません。
http://www.amiami.com/shop/ProductInfo/product_id/158244/
今回のシークレットはなんだろうか・・・

------------------------------------------------------------
項目3 privateのコンストラクタでインスタンス化不可能を強制する

staticなメソッドとstaticなフィールドだけからなるクラスを作成するケースとして、
java.lang.Math、java.util.Arrays、java.Util.Collectionsのようなユーティリティクラスを作成する場合が挙げられます。

こういったクラスはインスタンス化する設計にはなっていません。(インスタンス化するメリットや意味がありません)しかし、明示的にコンストラクタを定義していない場合、コンパイラはpublicでパラメータ無しのデフォルトコンストラクタを提供します。つまり、インスタンス化するためのクラスではないのにインスタンス化が可能になってしまいます。

クラスを抽象化することでインスタンス化不可能を実現しようとしてもうまくいきません。そのクラスのサブクラスでインスタンス化可能とすることができるためです。更に、継承させるために設計されていると使用者に誤解を与えます。

そこで、ベストな方法としてprivateなコンストラクタを定義する、という方法があります。

public class BearBrick {
 private BearBrick() {}
}

「インスタンス化させたくない!」という意図をコメントしておくと親切です。

注意点として、privateなコンストラクタのみを保持するクラスはサブクラスが作成できません。
明示的、あるいは暗黙的に、すべてのコンストラクタはアクセス可能なスーパークラスのコンストラクタを呼び出さなければならないためです。
------------------------------------------------------------

参考に・・・

------------------------------------------------------------
java.lang.Math
------------------------------------------------------------
public final strictfp class Math {

/**
* Don't let anyone instantiate this class.
*/
private Math() {}

------------------------------------------------------------

------------------------------------------------------------
java.util.Arrays
------------------------------------------------------------
public class Arrays {
// Suppresses default constructor, ensuring non-instantiability.
private Arrays() {
}
------------------------------------------------------------

本ではユーティリティクラスに対して言及してましたが、全章でもあったように、シングルトンとして設計されたクラスでもprivateコンストラクタは使用しますですね。

どうでもえーですが、Mathのコンストラクタはprivateなのにjavadeoc形式なんですね。僕もMath派な人です。

2010年4月9日金曜日

oracle データベースバッファキャッシュ2

食中毒になった。色々出てしまった・・。あんなに辛かったのは小6の時に40度の熱が出て以来だ。
来週の水曜の勉強会のために調べた内容を一部公開!

----------------------------------------------------------
■ データベースバッファキャッシュの構成
----------------------------------------------------------
データベースバッファキャッシュは以下の2つのリストで管理されています。
・ 書き込みリスト
・ LRUリスト

書き込みリストは使用済みバッファを保持します。
使用済みバッファとは、「修正されたが、まだディスクに書き込まれていないデータを含むバッファ」を指します。
LRUリストは、使用可能バッファ、使用中バッファおよび書込みリストに移動していない使用済バッファを保持します。
使用可能バッファとは、空、もしくはデータファイルと同期がとれているブロックが含まれるバッファを指します。
使用中バッファとは、現在アクセスされているバッファを指します。
LRUリストに含まれる使用済みバッファは特定のタイミングで書き込みリストへ移動します。

LRUリストにはLRU側とMRU側が存在し、LRU側であるほど、そのバッファに含まれるブロックの要求頻度が低く、
MRU側であるほど、そのバッファに含まれるブロックの要求頻度が高いことを表します。

----------------------------------------------------------
■ データベースバッファキャッシュの仕組み
----------------------------------------------------------
ユーザープロセスからデータの要求があると、まず、サーバープロセスは、データベースバッファキャッシュ内のデータを検索します。
キャッシュ内にデータが見つかった場合(キャッシュヒット)、プロセスは、キャッシュからデータを返します。
キャッシュヒットが発生すると、そのブロックは、LRUリストのMRU側へ移動します。

さらに多くのバッファがMRU側へ移動されるにつれて、アクセスが古いバッファはLRU側へ移動していきます。
(使用頻度の低いバッファほどLRU側へ移動しやすいです)

キャッシュ内にデータが見つからなかった場合(キャッシュミス)、
プロセスはディスク上のデータファイルからキャッシュ内のバッファにデータブロックをコピーする必要があります。
データブロックをコピーするには、コピーしたブロックを保持するためのバッファ(使用可能バッファ)を見つける必要があります。
その際、プロセスはLRUリストのLRU側から使用可能バッファの検索を行います。
検索は、以下の何れかの条件を満たすまで続きます。
・ 使用可能バッファを見つける
・ 検索したバッファ数が制限閾値に達する
LRUリスト検索時に使用済みバッファを見つけた場合、そのバッファを書き込みリストへ移動してから検索を続けます。
この際、書き込みリストのサイズが閾値を越えると、書き込みリスト上の使用済みバッファがDBWRによってディスクに書き込まれます。
使用可能バッファが見つかると、プロセスはディスクからバッファにデータブロックを読み込んで、バッファをLRUリストのMRU側へ移動します ※1

使用可能バッファが見つからず、検索したバッファ数が制限しきい値に達すると、プロセスはLRUリストの検索を停止し、データベースライターに使用済みバッファをデータファイルに書き込むよう指示します。
この際、使用済みバッファの書き込みリストへの移動は発生せず、直接LRUリストからディスクへの書き込みが行われます。
こうすることで、使用可能バッファを確保し、そこにデータブロックを読み込みます。

※1
厳密には、インデックススキャンによるランダムリードが実施された場合はMRU側とLRU側の境目にあるコールドポインタという位置に挿入されます。
フルスキャンによるシーケンシャルリードが実施された場合はLRU側に挿入され、次の瞬間には破棄される状態となります。
こういった仕組みにより、本当に重要なデータがLRUリストから押し出されることを防ぎます。
-------------------------------------------------------------------------------------
実際は図もいれてるからもっとわかりやすいだよ!でも掲載の仕方が分からない。みんなすごいなー

あと、まえの投稿でデータベースバッファキャッシュのキャッシュを消す方法を書いたけど、正確にsqlの性能を調べるには、ディクショナリキャッシュ、ライブラリキャッシュの情報も消す必要があるはずなので、以下のコマンドも実行したほうが良い気がしてきた。

ALTER SYSTEM FLUSH SHARED_POOL
(共有プールのクリア)

本番環境で・・(略

2010年4月5日月曜日

oracle データベースバッファキャッシュ1

来週の水曜にプロジェクトで実施してる勉強会の発表があるので、えふぇくちぶjavaが停止中。
ブログの更新を止めたくないのでjava意外の話題で更新してみます。

---- oracle データベースバッファキャッシュについて ----

データベースバッファキャッシュとは、ディスク(データファイル)への書き込み、読み込みを減らす(ディスクI/Oの低減)目的で使用されるメモリ領域。

ユーザープロセスからデータの要求があると、まず、サーバープロセスは、データベースバッファキャッシュ内のデータを検索する。

キャッシュ内にデータが見つかった場合(キャッシュヒット)、サーバープロセスは、データをメモリから直接読み取ることができる。

キャッシュ内にデータが見つからなかった場合(キャッシュミス)プロセスはデータにアクセスする前に、 ディスク上のデータファイルからキャッシュ内のバッファにデータブロックをコピーする必要がある。

キャッシュヒットによるデータのアクセスは、ディスクI/Oが発生しないため、キャッシュミスによるデータのアクセスよりも高速。

SQLの性能を調べてる時とか、キャッシュをクリアしないと正確な実行時間がわからないので注意。

ALTER SYSTEM FLUSH BUFFER_CACHE

でデータベースバッファキャッシュがクリアされまする。(10gから)
9iの場合はインスタンス停止が必要らしい。ふべんだね!
注意:データベースバッファキャッシュは全てのプロセスで共有されているため、本番環境とかでやると死ねます。