メインコンテンツまでスキップ

「Java」タグの記事が35件件あります

全てのタグを見る

Java: ローカルホスト名とIPアドレスを取得 - InetAddress.getLocalHost()メソッド

· 約1分
Yu Sasaki
Enterprise Security Manager / Advisor

ローカルのホスト名とIPアドレスの取得方法です。 InetAddress.getLocalHost()メソッドでInetAddressクラスの唯一のインスタンスを取得し、getHostName()でホスト名を、getHostAddress()メソッドでIPアドレスをそれぞれ取得します。 これらの情報はコンピュータ一台に対して通常一つしかないので(LANカードが二枚以上さされている場合を除いて)InetAddressクラスはSingletonパターンを採っているようです。あー、アドレスを二つ以上持っている場合の手順忘れてしまいました。後でさらっておこう。

ソースコード

import java.net.InetAddress;
import java.net.UnknownHostException;
public class ShowInetAddress {
public static void main(String[] args) {
// ローカルホスト名とIPアドレスを取得
try {
InetAddress addr = InetAddress.getLocalHost();
InetAddress addr2 = InetAddress.getLocalHost();
if (addr.equals(addr2)) System.out.println("addrとaddr2は同じインスタンス");
System.out.println("Local Host Name: " + addr.getHostName());
System.out.println("IP Address : " + addr.getHostAddress());
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}

実行結果の一例

addrとaddr2は同じインスタンス
Local Host Name: Yukun-PC
IP Address : 192.168.1.10

Android: 10進数→2進数変換アプリ

· 約3分
Yu Sasaki
Enterprise Security Manager / Advisor

試しに下図のような簡素な10進2進変換アプリを作ってみました。 以下は書いてみたコード。

ソースコード

res/values/strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Integer to Binary Calculator</string>
<string name="label_description">10進数を2進数に変換表示</string>
<string name="label_integer">10進数:</string>
<string name="label_binary">2進数:</string>
<string name="button_convert">変換</string>
</resources>

res/layout/main.xml

<?xml version="1.0" encoding="utf-8"?>
<linearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<textView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/label_description"
/>
<linearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<textView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_integer"
/>
<editText android:id="@+id/text_integer"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:numeric="integer"
android:maxLength="9"
android:text=""
/>
</linearLayout>
<button android:id="@+id/button_convert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/button_convert"
/>
<textView android:id="@+id/label_result"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text=""
/>
</linearLayout>

ITBCalculatorActivity.java

package info.yukun.android.itbcalc;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class ITBCalculatorActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) { // アクティビティが生成される際に必ず呼び出される
super.onCreate(savedInstanceState);
setContentView(R.layout.main); // UIのレイアウトを設定
// 指定したリソース(R.java)インデックスからビュー(res/layout/main.xml)内のidのコンポーネント(ここではButton)インスタンスを取得
Button button = (Button) findViewById(R.id.button_convert);
button.setOnClickListener(convertToBinary); // ボタンが押された際のイベントを登録
}
// 登録するイベントリスナーはView.OnClickListenerを実装
private View.OnClickListener convertToBinary = new View.OnClickListener() {
public void onClick(View view) { // ボタンが押されたときに呼び出されるメソッド
EditText textInteger = (EditText) findViewById(R.id.text_integer); // 入力値が入っているコンポーネントを取得
String input = textInteger.getText().toString();
if (input.equals("")) return;
int intValue = Integer.parseInt(input);
String binValue = Integer.toBinaryString(intValue); // 10進数整数を2進数文字列に変換
TextView labelResult = (TextView) findViewById(R.id.label_result); // 結果を表示するTextViewを取得
labelResult.setText("2進数:" + binValue);
}
};
}

Androidアプリを書いてみて

EditText android:id="@+id/text_integer"の属性android:maxLengthを"9"としたのは、変換メソッドInteger.toBinaryString(int value)の引数が32bit整数の為です。本当は属性値を10としてonClickメソッド内で32bitの範囲内(-2 147 483 647 ~ 2 147 483 647 = (2^31) - 1)か否かを判定したほうが良いと思うのですが、今回は端折りました。 ビジュアルエディタやXMLでUIのレイアウトを作っていくというのはFlexのmxmlに似てて取っ掛かり易い感じです。

java.io.StreamCorruptedExceptionが発生した原因とその解決策の一例

· 約3分
Yu Sasaki
Enterprise Security Manager / Advisor

以前というかこの頃Javaで簡単な分散処理サーバ・クライアントシステムのモデルを実装中にこのjava.io.StreamCorruptedExceptionという例外が発生。

StreamCorruptedExceptionの発生原因

結論から言えば、恐らく実行中のスレッドの数がマシンスペックに対して多すぎたのではないかと推定(推定どまり)。 サーバが複数のクライアントを受け付けるので、クライアントのソケット接続(accept時)毎にスレッドを生成する方法を採った。この時はブロッキング型のモデル(この頃ノンブロッキング型は知らなかった)。 例外の発生状況はサーバプログラムをテスト動作時、絶えず約1000クライアントからのリクエストを受け付け、かつレスポンス等を行った場合。なお送受信データはシリアライズされたオブジェクトで、サイズは平均5KB。その時テストマシンで走らせたスレッド数が約5000。 シリアライズの復元の問題かと考えたが、送受信するオブジェクトのクラスとそのserialVersionUIDは揃えており、500クライアント位ではこれといった異常なく動作していた。 実際にある時間のクライアントの送信データ数とサーバの受信データ数を確認してみたら、両数値の差が生じていて、約200リクエストがソケット部分で溜まったところでダウンしました。 スレッド数が多いと、その分スレッドの切り替えが頻発したり、待たされるスレッドが出てくる。その為、ソケットの部分で受信データが許容量以上に溜まって、I/Oのどこかがオバーフローか変になった可能性もある。

例外発生時のスタックトレースの一部抜粋

java.io.StreamCorruptedException: unexpected reset; recursion depth: 1
at java.io.ObjectInputStream.handleReset(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.skipCustomData(Unknown Source)
at java.io.ObjectInputStream.readNonProxyDesc(Unknown Source)
at java.io.ObjectInputStream.readClassDesc(Unknown Source)
at java.io.ObjectInputStream.readOrdinaryObject(Unknown Source)
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)

また、これ以外にOptionalDataExceptionという例外も併発しましたが、これも同じ原因かと推定。

java.io.OptionalDataException
at java.io.ObjectInputStream.readObject0(Unknown Source)
at java.io.ObjectInputStream.readObject(Unknown Source)

試した解決策

スレッド数を抑える

これは解決策とは言えないが、マシンスペック(CPU[コア]数やクロックなど)に対してスレッド数が多すぎるのが問題だと推定したので、ある程度スレッド数を抑えて運用したところ例外は発生しなくなった。

もう一度接続しなおす

例外をtry-catchブロックでキャッチできるので、それに合わせて接続しなおしてリクエストorレスポンスを再送することで一時対処した。

ノンブロッキング型にする

nio(New I/O)ライブラリが用意されているので、それを用いたノンブロッキング型の機構にして、スレッドプールを用意することでスレッド数やコンテキストスイッチ、オーバヘッドをある程度抑えることが可能と考えるが、まだ未検証。 どちらにせよ、根本原因の究明にはkernel dump等の情報を取って解析していく必要がある。。。

検索エンジンを実装 (6)NOT演算

· 約3分
Yu Sasaki
Enterprise Security Manager / Advisor

今回は集合演算のNOT演算ついて紹介します。この処理は、例として検索の際に「sky NOT rain」と指定すると、"sky"というキーワードを含むページから"rain"を含むページを除きます。

NOT演算処理の概要

NOT演算結果

上の図から、ある2つの語の転置インデックスリストをA, Bとします。ここで、リスト要素をそれぞれa, b(整数)とし演算結果を格納するリストをCとするとき、NOT演算は主に以下の処理内容を繰り返します。

  1. if a < b then 要素aをCの末尾に追加し、aにリストAの次の要素を代入
  2. if a = b then A, Bが指す次の要素をa, bに代入
  3. if a > b then bにリストBの次の要素を代入

ソースコード

今回はNOT演算処理を行う部分(メソッド)のみを示します。後で示す実行結果は、前のブログラムをベースにintersect()メソッドの挿入箇所を今回のものに置き換えたものです。

import java.util.ArrayList;
/**
* 検索エンジンのNOT演算
*/
public class BooleanRetrieval {
/**
* NOT演算処理
* @param postsSet 全ての検索語の転置インデックスリスト
* @return 演算後の転置インデックスリスト
*/
public static ArrayList<integer> subtract(ArrayList<arrayList<integer>> postsSet) {
ArrayList<integer> result; // 最終演算結果
if (postsSet == null) return null;
int len = postsSet.size();
if (len == 0) return null;
else if (len == 1) return postsSet.get(0);
result = postsSet.get(0);
for (int i = 1; i < len; i++) {
result = subtract(result, postsSet.get(i));
}
return result;
}
public static ArrayList<integer> subtract(ArrayList<integer> p1, ArrayList<integer> p2) {
ArrayList<integer> answer = new ArrayList<integer>(); // 2語の演算結果
int len1 = p1.size();
int len2 = p2.size();
int i=0, j=0;
while (i<len1 && j<len2) {
int diff = p1.get(i) - p2.get(j);
if (diff == 0) {
i++; j++;
} else if (diff < 0) {
answer.add(p1.get(i));
i++;
} else {
j++;
}
}
while (i<len1) { answer.add(p1.get(i++)); }
while (j<len2) { answer.add(p2.get(j++)); }
return answer;
}
}
単語 freq, docID
15 : 1, [2]
5 : 1, [2]
After : 1, [1]
As : 1, [1]
I : 3, [0, 1, 2]
<中略>
that : 1, [2]
the : 3, [0, 1, 2]
think : 3, [0, 1, 2]
this : 1, [2]
time : 2, [0, 2]
to : 2, [0, 1]
touch : 1, [1]
tremendously : 1, [1]
uncertainty : 1, [1]
use : 1, [2]
vigorous : 1, [1]
wanna : 1, [1]
well : 1, [1]
what : 1, [0]
when : 1, [1]
where : 1, [0]
why : 1, [1]
with : 1, [1]
検索語: the think
結果 :文書中に存在しません。
検索語: the use
結果 :文書ID [0, 1]に存在します。
検索語: the to
結果 :文書ID [2]に存在します。
検索語: quit

おー、けっこうたのしくなってきましたねー。

Java: 文字列の先頭・末尾の文字を削除するstrip()メソッド

· 約3分
Yu Sasaki
Enterprise Security Manager / Advisor

テキストマイニングを行う際、文書を単語集合に区切ったのはいいけれど、単語の先頭・末尾に以下のような文字が入っている場合は辞書に格納する際に削除したいですね。

Hello!
page."
"Hi,

単語の前後に複数の記号((ピリオドやクォーテーション、カンマやコロンなど))が入り混じった文字列から、それらを刈り取る方法を採り上げます。0からゴリゴリ文字の条件判定文を書いたり、正規表現でマッチしたときに刈り取りしたりするのも良いですが、オープンソースで今回のケースにマッチしたクラスメソッドがありますので、その使い方と仕組みを簡単に紹介します。

StringUtils#strip(String str, String stripChars)

ソース・バイナリ共にApache CommonsLang Downloadsのページからダウンロードできます。

StringUtils#strip(String str, String stripChars)メソッド引数は、
+第一引数:刈り取り対象文字列(テキスト)
+第二引数:刈り取る文字
例えば、

str = "Hi,]";
stripChars = "],";
result = strip(str, stripChars);

の時のresultの中の文字列はHiとなります。

stripメソッドは内部でstripStartとstripEndメソッドを実行しています。前者は文字列の先頭部分の刈り取りを、後者は末尾部分の刈り取りを行っています。
以下に、stripEndのソースを引用します。

public static String stripEnd(String str, String stripChars) {
int end;
if (str == null || (end = str.length()) == 0) {
return str;
}
if (stripChars == null) {
while ((end != 0) && Character.isWhitespace(str.charAt(end - 1))) {
end--;
}
} else if (stripChars.length() == 0) {
return str;
} else {
while ((end != 0) && (stripChars.indexOf(str.charAt(end - 1)) != -1)) {
end--;
}
}
return str.substring(0, end);
}

ここで重要な処理部分はソース最後の、

while ((end != 0) && (stripChars.indexOf(str.charAt(end - 1)) != -1)) {
end--;
}
}
return str.substring(0, end);

の部分です。
刈り取り対象文字列の末尾から順に文字を調べていきますが、その文字を指すパラメータにendを、その初期値はテキストの文字列長でループごとにデクリメントされていきます。そのendが指す文字がstripCharsに含まれているか否かを判定しているところが、

stripChars.indexOf(str.charAt(end - 1)) != -1

ですね。最終的にsubstringメソッドで部分文字列を取得することで刈り取っています。
これ書いた人上手いなー。indexOfやcharAtの使い道の幅が広がった感じがします。

検索エンジンを実装 (5)OR演算

· 約4分
Yu Sasaki
Enterprise Security Manager / Advisor

前回がAND演算でしたので今回はOR演算ついて紹介します。今記事で紹介している演算アルゴリズムよりも高効率なものは存在するようですが、今回は割愛します。

OR演算処理の概要

OR演算結果

上の図から、ある2つの語の転置インデックスリストをA, Bとします。ここで、リスト要素をそれぞれa, b(整数)とし演算結果を格納するリストをCとするとき、OR演算は主に以下の処理内容を繰り返します。

  1. if a < b then 要素aをCの末尾に追加し、aにリストAの次の要素を代入
  2. if a = b then 要素aをCの末尾に追加し、A, Bが指す次の要素をa, bに代入
  3. if a > b then 要素bをCの末尾に追加し、bにリストBの次の要素を代入

ソースコード

今回はOR演算処理を行う部分(メソッド)のみを示します。後で示す実行結果は、前回ブログラムをベースにintersect(postsSet)の箇所を今回のものに置き換えたものです。

import java.util.ArrayList;
import java.util.Collections;
/**
* 検索エンジンのOR演算
*/
public class BooleanRetrieval {
/**
* OR演算処理
* @param postsSet 全ての検索語の転置インデックスリスト
* @return 演算後の転置インデックスリスト
*/
public static ArrayList<integer> union(ArrayList<arrayList<integer>> postsSet) {
ArrayList<integer> result; // 最終演算結果
if (postsSet == null) return null;
int len = postsSet.size();
if (len == 0) return null;
else if (len == 1) return postsSet.get(0);
result = postsSet.get(0);
for (int i = 1; i < len; i++) {
result = union(result, postsSet.get(i));
}
return result;
}
public static ArrayList<integer> union(ArrayList<integer> p1, ArrayList<integer> p2) {
ArrayList<integer> answer = new ArrayList<integer>(); // 2語の演算結果
int len1 = p1.size();
int len2 = p2.size();
int i=0, j=0;
while (i<len1 && j<len2) {
int diff = p1.get(i) - p2.get(j);
if (diff == 0) {
answer.add(p1.get(i));
i++; j++;
} else if (diff < 0) {
answer.add(p1.get(i));
i++;
} else {
answer.add(p2.get(j));
j++;
}
}
while (i<len1) { answer.add(p1.get(i++)); }
while (j<len2) { answer.add(p2.get(j++)); }
return answer;
}
}
単語 freq, docID
15 : 1, [2]
5 : 1, [2]
After : 1, [1]
As : 1, [1]
I : 3, [0, 1, 2]
<中略>
should : 2, [0, 1]
so : 2, [1, 2]
some : 1, [2]
standard : 1, [0]
such : 2, [1, 2]
than : 1, [2]
that : 1, [2]
the : 3, [0, 1, 2]
think : 3, [0, 1, 2]
this : 1, [2]
time : 2, [0, 2]
to : 2, [0, 1]
touch : 1, [1]
tremendously : 1, [1]
uncertainty : 1, [1]
use : 1, [2]
vigorous : 1, [1]
wanna : 1, [1]
well : 1, [1]
what : 1, [0]
when : 1, [1]
where : 1, [0]
why : 1, [1]
with : 1, [1]
検索語: the when
結果 :文書ID [0, 1, 2]に存在します。
検索語: should so
結果 :文書ID [0, 1, 2]に存在します。
検索語: this touch
結果 :文書ID [1, 2]に存在します。
検索語: use than
結果 :文書ID [2]に存在します。
検索語: where why
結果 :文書ID [0, 1]に存在します。
検索語: quit

おー、なんだかもりあがってきましたねー。

話は変わりますが、最近よく実感していることの一つに、ソフトウェアの仕様が変更されると場合によっては設計を根本から変える必要があること。

そして、手がけているソフトウェアが今後アップグレードを繰り返していくと予測できるなら、現時点で取り組んでいる設計に費やす時間はほどほどに、ということです。OO分析設計を用いて保守性・拡張性を高めるのもいいけれど、その設計上での拡張が今後の仕様変更に耐えうるとは限りません。

勿論、最初の設計が肝心であることには変わりないですけれど。それでも留意。

検索エンジンを実装 (4)AND演算

· 約7分
Yu Sasaki
Enterprise Security Manager / Advisor

AND演算処理の概要

AND演算結果 上の図から、ある2つの語の転置インデックスリストをA, Bとします。ここで、要素をそれぞれa, b(整数)とし演算結果を格納するリストをCとするとき、AND演算は主に以下の処理内容を繰り返します。

  1. if a < b then aの次の要素をaに代入
  2. if a = b then 要素aをCの末尾に追加しA, Bが指す要素を一つ進める

プログラムの主な処理内容

  1. 検索対象テキストを単語に分割。
  2. 単語を転置インデックスに登録。ここで、1単語あたりに格納する情報は、その単語の出現頻度とその文書ID。転置インデックスのデータ構造はTreeMapを使用しkeyに単語、valueはIndexRecordでputします。
  3. ユーザからの標準入力をパースしAND演算(Intersectメソッドで実現しています)。

以下に、ソースコードと実行結果を示します。

IndexRecord.java

1つのトークン(単語)に対するインデックス情報(docIDリストや出現頻度情報)

import java.util.ArrayList;
/*
* 1つのトークン(単語)に対するインデックス情報
*/
public class IndexRecord {
// 出現文書IDリスト(通常ソートの必要あり)
private ArrayList<integer> posts;
// 出現頻度(今回は同一doc内の頻度はカウントしない)
private int freq;
public IndexRecord(int id) {
posts = new ArrayList<integer>();
posts.add(id);
freq = 1;
}
/** docIDをリストに追加(今回は同一docIDのtermはカウントしない) */
public void addDocID(int id) {
if (!existDocID(id)) {
posts.add(id); // docIDの追加
freq++; // 出現頻度のカウントアップ
}
}
/** docIDが既にリスト中に存在するか否か */
public boolean existDocID(int id) {
return (posts.indexOf(id) != -1) ? true : false;
}
public String toString() {
String str = freq + ", "+ posts;
return str;
}
public ArrayList<integer> getPosts() {
return posts;
}
}

BooleanTest.java (AND演算のテストプログラム)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.StringTokenizer;
import java.util.TreeMap;
/**
* 検索エンジンのAND演算処理
* Web page: https://yukun.info/
* license GPL
*/
public class BooleanTest {
// 検索対象テキスト
static String doc0 = "It is meaningless only to think my long further aims idly. "+
"It is important to set my aims but at the same time I should confirm my present condition. "+
"Unless I set the standard where I am in any level, I'll be puzzled about what I should do from now on. It's in my case.";
static String doc1 = "Today, I enjoyed playing with friends daytime. "+
"After enjoying, I got back to my daily life with an vigorous power. "+
"I should think so, but why did I feel touch of uncertainty and regret? "+
"I wanna enjoy myself and another tremendously during the day when I've played. "+
"Well, As well as I commit play to quality, I'll choose such kinds of play.";
static String doc2 = "I'll manage the limited time in a day. "+
"I think that I divide the time into some intervals such as 5 minutes, "+
"15 minutes and more than one hour and so on. I'll make use of this character of the interval.";
public static void main(String[] args) {
ArrayList<string> docIDlist = new ArrayList<string>();
// 文書を格納
docIDlist.add(doc0);
docIDlist.add(doc1);
docIDlist.add(doc2);
StringTokenizer st[] = new StringTokenizer[3];
String stripChars = ".,:;?!"'[]{}()"; // 除外文字
// 文字列を空白で区切るよう設定
for (int i = 0; i < st.length; i++) {
st[i] = new StringTokenizer(docIDlist.get(i), " ");
}
// 転置インデックス用のMap
TreeMap<string, IndexRecord> termMap = new TreeMap<string , IndexRecord>();
// 分割されたトークンを取得
for (int i = 0; i < st.length; i++) {
// ここでのパラメータiはdocIDを指すことと同じ
while (st[i].hasMoreTokens()) {
// 文字列トークンの先頭・末尾の文字をフィルタリング
// org.apache.commons.lang.StringUtilsクラスを使用
// http://commons.apache.org/proper/commons-lang/
String term = StringUtils.strip(st[i].nextToken(), stripChars);
//System.out.println("値 : " + term);
if(termMap.containsKey(term)) {
// 登録されているtermならdocIDの追加とカウントアップ
IndexRecord ir = termMap.get(term);
ir.addDocID(i);
termMap.put(term, ir);
} else {
// termMapに登録されていないtermならdocIDと合わせて登録
termMap.put(term, new IndexRecord(i));
}
}
} // for loop ends
// termMapのデバッグプリント
System.out.println("単語 freq, docID");
for (String part : termMap.keySet()) {
System.out.printf("%-12s : %sn", part, termMap.get(part));
}
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String words = "";
while (true) {
ArrayList<arrayList<integer>> postsSet = new ArrayList<arrayList<integer>>();
System.out.print("検索語: ");
try {
// ユーザからの標準入力を受付
words = br.readLine();
if (words.equals("quit")) break;
// 入力文字列をパース
StringTokenizer parser = new StringTokenizer(words, " ");
while (parser.hasMoreTokens()) {
String term = StringUtils.strip(parser.nextToken(), stripChars);
// termMapに登録されている単語か否か
if (termMap.containsKey(term)) {
postsSet.add(termMap.get(term).getPosts());
} else {
postsSet = null;
break;
}
}
// AND演算処理
ArrayList<integer> result = intersect(postsSet);
System.out.print("結果 :");
if (result == null || result.size() == 0)
System.out.println("文書中に存在しません。");
else
System.out.println("文書ID "+ result +"に存在します。");
} catch (IOException e) {
e.printStackTrace();
}
}
} // main() ends
// AND演算処理メソッド
public static ArrayList<integer> intersect(ArrayList<arrayList<integer>> postsSet) {
if (postsSet == null) return null;
int len = postsSet.size();
if (len == 0) return null;
else if (len == 1) return postsSet.get(0);
// postsSetを昇順にソート(演算回数の削減)
Collections.sort(postsSet, new FreqComparator());
ArrayList< Integer > result = postsSet.get(0);
for (int i = 1; i < len; i++) {
result = intersect(result, postsSet.get(i));
}
return result;
}
public static ArrayList<integer> intersect(ArrayList<integer> p1, ArrayList<integer> p2) {
ArrayList<integer> answer = new ArrayList<integer>();
int len1 = p1.size();
int len2 = p2.size();
for (int i=0, j=0; i< len1 && j < len2; ) {
if (p1.get(i) == p2.get(j)) {
answer.add(p1.get(i));
i++; j++;
} else if (p1.get(i) < p2.get(j)) {
i++;
} else {
j++;
}
}
return answer;
}
}

FreqComparator.java (ArrayList要素のソート用)

import java.util.ArrayList;
import java.util.Comparator;
public class FreqComparator implements Comparator<object>{
public int compare(Object o1, Object o2){
return ((ArrayList<integer>) o1).size() - ((ArrayList<integer>) o2).size();
}
}

実行結果

単語 freq, docID
15 : 1, [2]
5 : 1, [2]
After : 1, [1]
As : 1, [1]
I : 3, [0, 1, 2]
<中略>
set : 1, [0]
should : 2, [0, 1]
so : 2, [1, 2]
some : 1, [2]
standard : 1, [0]
such : 2, [1, 2]
than : 1, [2]
that : 1, [2]
the : 3, [0, 1, 2]
think : 3, [0, 1, 2]
this : 1, [2]
time : 2, [0, 2]
to : 2, [0, 1]
touch : 1, [1]
<中略>
検索語: think that
結果 :文書ID [2]に存在します。
検索語: wanna play to
結果 :文書ID [1]に存在します。
検索語: the java
結果 :文書中に存在しません。
検索語: should so
結果 :文書ID [1]に存在します。
検索語: time to
結果 :文書ID [0]に存在します。
検索語: well only
結果 :文書中に存在しません。
検索語: the time
結果 :文書ID [0, 2]に存在します。
検索語: quit

おー、なんだか楽しくなってきましたね。

文字列の区切り方

今回は検索対象テキストを英文に絞ったため、テキスト中の空白文字で区切ることでトークンを抽出できました。対して、日本語テキストの場合は区切り記号等は無い為、n-gramか形態素辞書などを用いてトークンに区切ることで実現できます。日本語文の区切り方は色々ありますが、中でも簡単な方法は、文字種(英文字、記号、ひらがな、カタカナ、漢字)の違いを区切りの境界と捉える方法です。 余談ですが、ブラウザやエディタ等で文字の上でダブルクリックするとカーソル下の文字列が選択状態になりますが、その範囲を決定する際に上述の方法が応用されているようです。ソフトによってはトリプルクリックするとカーソル下の行全体が選択状態になります(使うと編集が楽です)。

JavaプログラムからExcite翻訳を利用

· 約3分
Yu Sasaki
Enterprise Security Manager / Advisor

POSTメソッドを用いてWebページのフォームにリクエストを送信し、そのレスポンスを取得するプログラム例として、エキサイト 翻訳を利用してみます。 送信クエリの1つは翻訳言語設定、2つ目は翻訳対象文字列でレスポンスのWebページから翻訳された文字列を抽出します。

ソースコード

import java.net.*;
import java.util.regex.*;
import java.io.*;
/**
* Excite翻訳(http://www.excite.co.jp/world/)を利用するクラス
*/
public class ExciteTrans {
private String direction; // 翻訳する言語設定 "ENJA" or "JAEN"
/** テスト用main() */
public static void main(String[] args) {
ExciteTrans et = new ExciteTrans();
System.out.println(et.getTransText("Hello!")); // 翻訳対象テキスト
}
/** コンストラクタ */
public ExciteTrans() { direction ="ENJA"; }
public ExciteTrans(String str) {
if (str.equals("JAEN") || str.equals("ENJA"))
direction = str;
else
direction ="JAEN";
}
/**
* テキストを翻訳
* @param before 翻訳前のテキスト
* @return 翻訳後のテキスト
*/
public String getTransText(String before) {
String afterText = null; // 翻訳されたテキスト
try {
// URLクラスのインスタンスを生成
URL exciteURL =
new URL("http://www.excite.co.jp/world/english/");
// 接続します
URLConnection con = exciteURL.openConnection();
// 出力を行うように設定
con.setDoOutput(true);
// 出力ストリームを取得
PrintWriter out = new PrintWriter(con.getOutputStream());
// クエリー文の生成・送信
out.print("before={"+ before +"}&wb_lp={"+ direction +"}");
out.close();
// 入力ストリームを取得
BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
// 一行ずつ読み込む
String aline;
// 抽出用の正規表現
String regex = "<textarea [^>].*after.*>(.*)";
Pattern pattern = Pattern.compile(regex);
while ((aline = in.readLine()) != null) {
Matcher mc = pattern.matcher(aline);
if(mc.matches()) {
afterText = mc.group(1);
}
}
in.close(); // 入力ストリームを閉じる
} catch (IOException e) {
e.printStackTrace();
}
return afterText;
}
}

実行結果

こんにちは!

Java MSN Messenger Library (JML)で翻訳ロボを作る

Java MSN Messenger Library (JML)とは Windows Live Messenger 上の通信プロトコルMSNP8-MSNP12をサポートするライブラリです。主に、メンバーの会話に自動応答するチャットボット(Chat Bot)や人工無脳などを作る際に用いられます。 今回は、ライブラリに付属しているサンプルプログラムのEchoMessengerクラスにこの翻訳クラスをかませて、メンバーの発言を翻訳し、その文字列色をランダムで選択した色で応答するチャットボットを作成してみました。 実行結果は下図になります。 ![JML翻訳ロボの実行結果例](./naiTrans-e1273383658134.png) コンストラクタでの翻訳言語設定が逆でも、文字列が英語or日本語に統一されていれば、Excite翻訳側がそれに合わせて適当な言語で翻訳するみたいです。 最後に、

礼儀正しくクローリングする際は

  • robots.txtやメタタグを守るように
  • 相手サーバに負荷をかけすぎないように
  • 相手Webサイトポリシー(利用規約)を守るように
  • 頻繁に使いたいモノなら、同機能のWebサービスを使うこと(利用規約内で)
うーん、今回のようなプログラムの利用はあまりよくないようでね。

java.lang.OutOfMemoryErrorが発生する原因とその解決法の一例

· 約1分
Yu Sasaki
Enterprise Security Manager / Advisor

JVMがGCを行えるように、開放するインスタンスへの参照を切っていたのだけれど、なぜか例外が投げられ続けていました。色々調べてみたら、java.io.ObjectOutputStream#writeObject(Object obj)の部分で、書き出されたobjの状態が保持され続けるので、いつまでたってもGCが始まらなかったのが原因でした。 解決法は、java.io.ObjectOutputStream#reset()メソッドでストリームが保持している状態を無効にすることで、不要インスタンスをGCの対象に入れることでした。

検索エンジンを実装 (3)文書内の検索語を特定

· 約6分
Yu Sasaki
Enterprise Security Manager / Advisor

今回実装したことは、

  • IndexRecordクラスにフィールド更新用のメソッドやハッシュフィールドを追加(今後改善の必要大)。
  • 検索語を含んでいるファイルをピックアップする(色々と無駄な部分あり)。

辺りです。

後述に現在の問題点とその解決案を考えてみましたが、先ずはソースコードと実行結果(デバッグプリント)を示します。

追記:こちらに→ 検索エンジンを実装 (4)AND演算 完成版を書きましたので、そちらをご覧ください。↓以下、黒歴史(>_<)↓

IndexRecord.java

import java.util.ArrayList;
import java.util.TreeMap;
public class IndexRecord {
// 総出現回数
Integer count;
// 出現したファイルID
ArrayList<integer> file_ids;
// ファイル内の出現位置(ファイルの先頭からのオフセット)
ArrayList<integer> word_poses;
// ファイルIDごとに出現数をカウント<ファイルid, 出現数>
TreeMap<integer, Integer> idcntMap;
private IndexRecord() {}
public IndexRecord(int id, int pos) {
count = 1;
file_ids = new ArrayList<integer>();
file_ids.add(id);
word_poses = new ArrayList<integer>();
word_poses.add(pos);
idcntMap = new TreeMap<integer, Integer>();
idcntMap.put(id, 1);
}
public void renewal(int id, int pos) {
count++;
file_ids.add(id);
word_poses.add(pos);
if(idcntMap.containsKey(id)){
Integer idcnt = idcntMap.get(id);
idcnt++;
idcntMap.put(id, idcnt);
} else {
idcntMap.put(id, 1);
}
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(count);
for(int i = 0; i < file_ids.size(); i++){
sb.append(" (" + file_ids.get(i) + ", " + word_poses.get(i) + ")");
}
sb.append(" "+ idcntMap);
return sb.toString();
}
}

Make2Gram.java

import java.io.File;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.TreeMap;
public class Make2Gram{
public static final boolean DEBUG = true; // デバッグ用フラグ
public static void main(String[] args) throws IOException{
if(args.length == 0){
System.out.println("引数にディレクトリ名を指定してください");
System.exit(1);
}
int N = 2; // bigram
// http://sattontanabe.blog86.fc2.com/blog-entry-55.html
// Java 再帰的にファイルを検索 / Chat&Messenger
// のクラスFileSearchを使用しています
FileSearch search = new FileSearch();
File[] files = search.listFiles(args[0], null); // 全てのファイルを取得
// ファイルIDとパスの対応表
TreeMap<integer, File> fileMap = new TreeMap<integer, File>();
ArrayList<string> docs = new ArrayList<string>();
for(int i=0; i < files.length; i++){
fileMap.put(i, files[i].getAbsoluteFile());
BufferedReader br = new BufferedReader(new FileReader(files[i]));
StringBuilder sb = new StringBuilder();
String line;
while((line = br.readLine()) != null)
sb.append(line);
br.close(); // ファイル内容(RAW)を格納
String text = sb.toString(); // ファイルの内容 改行抜き
docs.add(text);
}
//テキストの部分文字列とそのIndexRecordクラスを関連付けるMap
//TreeMapなのでMapのキーにした部分文字列でソートされる
TreeMap<string, IndexRecord> gramMap = new TreeMap<string, IndexRecord>();
for(int i = 0; i < fileMap.size(); i++){
// ファイルごとの処理
String text = docs.get(i);
for(int j = 0; j < text.length() - N; j++){
//テキストからN文字取り出す
String gram = text.substring(j, j + N);
if(gramMap.containsKey(gram)){
//gramMapに登録されてる文字列ならカウント等を増やす
IndexRecord ir = gramMap.get(gram);
ir.renewal(i, j);
gramMap.put(gram, ir);
}else{
//gramMapに登録されていない文字列なら登録
gramMap.put(gram, new IndexRecord(i, j));
}
}
}
//for(String part : gramMap.keySet())
//System.out.printf("%s : %sn", part, gramMap.get(part));
String input = "N文字"; // 検索語(e.g.)
if(DEBUG) System.out.println("input #=> "+ input);
String[] swords = new String[(input.length()+1)/2];
boolean odd = false; // 文字列長の偶奇判定
if (input.length() < 2){
System.out.println("2文字未満の処理は未実装");
System.exit(1);
}
// 検索文字列をN文字単位に分割
for(int i = 0, j = 0; i < input.length()-N; i += N, j++){
swords[j] = input.substring(i, i+N);
}
if ((input.length() & 1) == 1){ // 文字列長が奇数
swords[swords.length-1] = input.substring(input.length()-N, input.length());
odd = true;
}
if(DEBUG){
System.out.print("swords #=> ");
for(String part : swords) System.out.print(part +" ");
System.out.println();
} // [N文, 文字](e.g.)
TreeMap<integer, Integer> id_per_cnt = new TreeMap<integer, Integer>();
// N文字単位のIndexRecordを格納する配列
IndexRecord[] ng_records = new IndexRecord[swords.length];
// 2文字ごとにgramMapと照合
for(int i = 0; i < swords.length; i++){
if(!gramMap.containsKey(swords[i])) {
System.out.println(" 検索語:【"+ input +"】はありません。");
System.exit(1);
}
IndexRecord ir = gramMap.get(swords[i]);
ng_records[i] = ir;
TreeMap<integer, Integer> _idcntMap = ir.idcntMap;
for(Integer id : _idcntMap.keySet()){
if(id_per_cnt.containsKey(id)){
int cnt = id_per_cnt.get(id);
cnt += _idcntMap.get(id);
id_per_cnt.put(id, cnt);
} else {
id_per_cnt.put(id, _idcntMap.get(id));
}
}
if(DEBUG) System.out.println(" "+ swords[i] +":"+ ng_records[i]);
}
if(DEBUG) System.out.println("id_per_cnt #=> "+ id_per_cnt);
for(Integer id : id_per_cnt.keySet()){
// ↓以下の部分の評価は中途段階。出現位置を考慮に入れた判定に変更予定。
// それに伴い、IndexRecordのデータ構造は要変更。
if(id_per_cnt.get(id) / swords.length > 0){
if(DEBUG) System.out.println(" 検索語はファイルid["+ id +"]中に存在する可能性あり。");
String target_doc = docs.get(id);
int pos = target_doc.indexOf(input);
String mch = target_doc.substring(pos, pos + input.length());
System.out.println(" 照合文字列 : "+ mch);
}
}
}
}

実行結果(コマンドライン引数部分は省略)

input #=> N文字
swords #=> N文 文字
N文:2 (0, 1) (0, 42) {0=2}
文字:3 (0, 2) (0, 43) (1, 61) {0=2, 1=1}
id_per_cnt #=> {0=4, 1=1}
検索語はファイルid[0]中に存在する可能性あり。
照合文字列 : N文字

現在の問題点

IndexRecordクラス:ArrayList型ではフィールド(ファイルidと出現位置)の関係性を取りづらい。

解決案

IndexRecordクラスのフィールドにファイルidを主キーとして、その部分単語の全ての出現位置を求められるハッシュデータが必要かと考えました。

今回もうすうすは感じていましたが、データ構造を設計し間違えるとプログラム構造が煩雑になりやすいです。初めから仕様を明確にしておけばデータモデリングでミスることもなかったかな。

しばらくは、今後の実装機能の洗い出しとそれに対応できるクラス構造を考えてみようかな。また、N-gramに分割する処理部分は別クラスのインスタンスメソッドとしてまとめたほうが良いですね。並行してデザインパターンも復習しておこう。