社内se × プログラマ × ビッグデータ

プログラミングなどITに興味があります。

Eclipse 例外ブレークポイントで効率アップ?

Eclipseデバッグ機能で Exception Breakpoint というものがあるそうで、何をするものか分からなかったので使ってみました。
デバッグ画面で Add Java Exception Breakpoint と表示されるボタンをクリックすると、例外が登録できる。
今回は、"NullPointerException" を登録してみました。
ソースコード上では、 90 % 以上の確率で、"NullPointerException"が発生するようにしています。

それでデバッグ機能を動かしてみると、登録した例外が発生したところで、ブレークしてくれました。
f:id:blueskyarea:20170722002327g:plain

なるほど、例外はふつう、意図しないものなので、”どこで発生しているか分からない”。
でもこの機能を使えば、例外の発生箇所をすぐに見つけることができそうですね。

うーん。
ただ、 JUnit でも例外の発生箇所はわりとすぐに見つかったりするので、本当にかつやくする機会があるかは、まだ分からないのが正直なところです。^^;

テキストファイルを1行ずつ処理するには

input.txt がよみこむテキストファイルの場合

こんな感じです。

while IFS='' read -r line && [[ -n "$line" ]]; do echo $line; done < input.txt
なぜ、IFS='' するのですか?

IFSってのはbash環境変数で、デフォルトでは$' ¥t¥n'(スペース・タブ・改行)が入っています。
そのままだと、半角スペースとタブがかってに消されてしまうため、空文字を設定しています。

read -r のオプションは何のためですか?

バック・スラッシュをエスケープ文字とみなさないで、そのまま取得するため。

awk でパターンにマッチする行のみを抽出する

こんな感じでスラッシュで挟んで指定すれば良いらしい。

$ xwininfo | awk '/Width/'
  Width: 1360

ただ、単に抽出するだけなら、 grep で十分かも。

$ xwininfo | grep 'Width'
  Width: 1360

抽出した結果を変数に入れて処理するには便利そう。

$ xwininfo | awk '/Width/ {width = $2} END { print "width="width }'
width=1360

Java challenge cloneable

Java challenge 既出のコードです。
出力結果を考えてみます。

ソースコード
public class CloneableChallenge {
    public static void main(String[] args) throws CloneNotSupportedException {
        Human human17 = new Human("cells");
	Human human18 = (Human) human17.clone();
		
	System.out.println(human17.equals(human18));
    }
	
    static class Human implements Cloneable {
	Cell cell;
		
	public Human(String cellName) {
	    this.cell = new Cell(cellName);
	}
		
	@Override
	public Object clone() throws CloneNotSupportedException {
	    return super.clone();
	}
    }
	
    static class Cell {
	String name;
	public Cell(String cellName) {
	    name = cellName;
        }
    }
}
予想

"false" が出力される。
正直、Cloneable をちゃんと使ったことがないので、clone 自体のコードに問題があるか分からないです。
単純に Human クラスのインスタンスが正常に clone 出来たと仮定したら、 それぞれ別のメモリ上に存在するはず。

結果

"false" が出力された。
うーん。ただ、上記の理解で正しいのかが分からない。
実際にこの challenge をした人のコメントを見てみると、human17.cell.equals(human18.cell) では true が返ってくるらしい(手元の環境でも true になった)。

Human class は別々のオブジェクトだが、クラス変数である Cell は同じオブジェクトを参照しているということか。

以下のサイトも参考になりました。
promamo.com

試したソースコード
github.com

Java private フィールドをモックする

private フィールドをモックする junit 書きたいなと思って調べたら、すぐに出てきたのが mockito の whitebox.
以前使ったことがあった気がするけど、忘れていました。

とりあえず簡単なサンプルで
public class WhiteBoxExample {
	
	private String message = "This is example.";
	
	public WhiteBoxExample() {
	}
	
	public String getMessage() {
		return message;
	}
}

これの message 変数に格納されている内容を書き換えるということです。

junit の方は
public class WhiteBoxExampleTest {

        // 1つ目は何もせずに、そのまま出力させるテスト
	@Test
	public void testWithoutWhiteBox() {
		WhiteBoxExample wb = new WhiteBoxExample();
		assertTrue(wb.getMessage().equals("This is example."));
	}

        // 2つ目は whitebox を使って、値を書き換えてます
	@Test
	public void testWithWhiteBox() {
		WhiteBoxExample wb = new WhiteBoxExample();
		Whitebox.setInternalState(wb, "message", "overwritten the message.");
		
		assertTrue(wb.getMessage().equals("overwritten the message."));
	}
}

Whitebox.setInternalState(対象クラスのインスタンス, "モックするフィールド名", 置き換える値);
たったこれだけで private フィールドが置き換わってしまいました。
非常に強力なライブラリだと思いますが、何でも置き換えてしまうと、本来の仕様に沿ったテストから遠ざかってしまう気がします。
テストのコア部分を置き換えるのではなくて、そこに到達するまでのあくまで補助的に使った方が良いですね。
そもそも頼らないでテスト出来るように設計出来れば良いのかもしれませんが。

SparkSql で5教科の成績に評価を付けてみる

SparkSqlはSpark上でSQLを利用するためのコンポーネントです。
Sparkの分散環境上で大量データに対して高速なSQLを実行できます。

SparkSqlでソートする練習のために、5教科の成績に評価を付けるプログラムを書いてみました。
例えば、今5人(A君~E君)がいるとして、英語の成績が以下であるとします。
A君:100点
B君:80点
C君:60点
D君:40点
E君:20点

その順位に応じた評価点をつけます。
上記の場合、A君:5点、B君:4点、C君:3点、D君:2点、E君:1点という感じです。
これを5教科(国語、数学、英語、理科、社会)に対して行い、合計の評価点を計算します。

元の点数そのまま使えばいいやんという感じでもありますが、ソートして連番を付ける練習がしたかったので、こんな計算をするようにしています。

ソースコードはこちらgithub.com

SparkSql で扱うエンティティには getter を定義しなければならない!

今回、成績を格納するエンティティとして、RecordData というクラスを定義しましたが、必ず各フィールドに対して getter を定義する必要がありました。
最初は定義していなかったのですが、DataFrame に対して、printSchema() を実行しても、select 文を発行しても何のデータも得られませんでした。
どうやら、getter 経由でデータを取得してくる仕様のようです。

SparkSql でも Window関数が使える!

select 文の結果に対して連番を付与する場合、SQLではWindow関数のrow_numberという機能がありますが、SparkSqlでもそれが使えるようです。
DataFrame には zipWithIndex 関数もありませんし、これは便利ですね。

df.select(row_number().over(Window.partitionBy().orderBy(orderElement1))
Window関数を使うためには HiveContext が必要!

HiveContext なしで使おうとすると、以下のエラーメッセージが表示されてしまいました。

Exception in thread "main" org.apache.spark.sql.AnalysisException: Could not resolve window function 'row_number'. Note that, using window functions currently requires a HiveContext;

なので、SQLContext は HiveContext から作りました。

SQLContext sqlCtx = new HiveContext(jsc);

ただ、コンテキストの生成処理に体感でも数秒間時間がかかっている気がします。

実行確認

printSchema() の実行結果。

root
 |-- english: integer (nullable = true)
 |-- japanese: integer (nullable = true)
 |-- mathematics: integer (nullable = true)
 |-- name: string (nullable = true)
 |-- science: integer (nullable = true)
 |-- social: integer (nullable = true)

日本語の成績。このように点数の多い人から、高い score が付けられています。

+-----+-------+--------+
|score|   name|japanese|
+-----+-------+--------+
|    1| Edison|       3|
|    2|Clinton|      13|
|    3|    Abe|      51|
|    4| Donald|      63|
|    5|   Bush|      66|
+-----+-------+--------+

数学の成績。みんな低すぎでしょ。

+-----+-------+-----------+
|score|   name|mathematics|
+-----+-------+-----------+
|    1| Donald|          2|
|    2| Edison|          2|
|    3|   Bush|          6|
|    4|    Abe|          9|
|    5|Clinton|         40|
+-----+-------+-----------+

英語の成績。単なる乱数とは言え、0点が出てしまった。

+-----+-------+-------+
|score|   name|english|
+-----+-------+-------+
|    1| Donald|      0|
|    2|    Abe|     29|
|    3|Clinton|     40|
|    4|   Bush|     49|
|    5| Edison|     51|
+-----+-------+-------+

社会の成績。

+-----+-------+------+
|score|   name|social|
+-----+-------+------+
|    1| Donald|    12|
|    2|Clinton|    53|
|    3| Edison|    72|
|    4|    Abe|    84|
|    5|   Bush|    98|
+-----+-------+------+

最後は、理科の成績。

+-----+-------+-------+
|score|   name|science|
+-----+-------+-------+
|    1| Edison|      6|
|    2| Donald|      7|
|    3|Clinton|     61|
|    4|   Bush|     69|
|    5|    Abe|     90|
+-----+-------+-------+

そして、総合成績。totalScore は綺麗に3の倍数になりました。

+-------+----------+--------+-----------+-------+------+-------+
|   name|totalScore|japanese|mathematics|english|social|science|
+-------+----------+--------+-----------+-------+------+-------+
|   Bush|        21|      66|          6|     49|    98|     69|
|    Abe|        18|      51|          9|     29|    84|     90|
|Clinton|        15|      13|         40|     40|    53|     61|
| Edison|        12|       3|          2|     51|    72|      6|
| Donald|         9|      63|          2|      0|    12|      7|
+-------+----------+--------+-----------+-------+------+-------+
まとめ

今までは、DataFrame で出来ないことを RDD に変換してから処理して、また DataFrame に戻すみたいなことをしていたけど、SparkSql(DataFrame) のままでも出来ることは思っていたより多い。
変換処理のオーバーヘッドも減らせるだろうし、使っていくのが楽しみになってきました。

Apache ZooKeeper の基本について調べてみる

概要

・分散アプリケーションを構築する上では、同期、設定管理、グルーピング、名前管理などの機能が必要となる。

→これらの実装は複雑で面倒なもの

→Zookeeper はこれらの機能を提供してくれる

・ただし、Zookeeper が提供するのは、基本機能要素そのものではなく、基本機能要素を独自に実装できるようにするための、ファイルシステム的なAPIを提供する。

・基本機能要素を実装したものをレシピと呼ぶ。

→レシピは znode と呼ばれる小さいデータノードの操作で構成される

・ ZooKeeper はJavaで書かれており、 jvm 上で動作可能。

 

API

・ znode を生成、削除したり、znode にデータを格納するようなものが提供されている

・ ZooKeeper クライアントは、ZooKeeper サービスに接続し、セッションを確立する。

→セッションを通してAPIを呼び出す

 

注意点

・ znode のデータは部分的に書いたり、読み出したりすることはできない

→完全置き換え、完全読み出し

→原子性の確保とも言えるかも

 

znode のモード

・永続(persistent) znode

→意図的に delete しないと削除されない

→これを作成したクライアントセッションが無くなったとしても、維持しなければならない場合に役立つ

 

・短命(ephemeral) znode

→作成したクライアントがクラッシュしたり、接続がクローズされると自動的に削除される

→これを作成したクライアントセッションが有効な間だけ存在するべき情報を保持する場合に役立つ

→クライアントから意図的に削除することも可能。作成したクライアントでなくても削除可能。

→基本的に消えてしまう性質から、短命 znode には子ノードは作成できない。(今も?)

 

・シーケンシャル znode

永続 znode、短命 znode 何れもシーケンシャルにすることが可能。

→znode にユニークな番号を付与するのに便利