Jython を学ぶ(6)───JAR ファイルを動的にロードする
前回の記事で、 batik.jar をクラスパスに入れる時に環境変数を使わなければならなかったのが良くなかったので、JAR ファイルを動的ロードすることにします。
Java では煩雑なリフレクション API を使わなければならないのですが、動的型付けかつ動的言語である Python では全てがリフレクションのようなものです。
実際、 Jython では普通に import したクラスのメソッドもリフレクション API で呼び出されていると思います。
例:
#! jython # dload.py from java.io import File from java.net import URL from java.net import URLClassLoader import jarray urlClassLoader=URLClassLoader(jarray.array([File("batik.jar").toURL()], URL)) JSVGCanvas=urlClassLoader.loadClass("org.apache.batik.swing.JSVGCanvas") #以下、JSVGCanvas を普通に import した場合と同じように使える print JSVGCanvas jsvgCanvas=JSVGCanvas() print jsvgCanvas.toString() # ...
カレントディレクトリに batik.jar を置いて、
jython dload.py
六行目が少しわかりにくいかもしれません。
URLClassLoader のコンストラクタは JAR ファイルまたはディレクトリの URL の配列を引数にとります。
このクラスローダは引数に指定された URL からクラスとリソースを検索します。
ClassLoader#loadClass で指定した名前のクラスオブジェクトを得ることができます。
ここで Java ならリフレクション API でこのクラスオブジェクトを使う羽目になるのですが、Jython では普通に import した場合と同じように使えます。
今後この日記に載せる Jython プログラムでは標準ライブラリ以外の JAR ファイルは全て動的にロードしようと思います。
Jython を学ぶ(5)───Apache Batik の SVGCanvas で SVG を簡単に表示する
しばらく中断といっておきながら一日しか中断しないのであった。
どうも I/O 関連は考えが定まらないので、先に Apache Batik で SVG を表示する例を紹介します。
http://xmlgraphics.apache.org/batik/using/swing.html に載っている例の Jython 版です。
#! jython from java.lang import Runnable class PyCallableRunnable(Runnable): def __init__(self, pyCallable): self.pyCallable=pyCallable return def run(self): self.pyCallable() return def initializeAWTUI(): from javax.swing import JPanel from java.awt import BorderLayout panel=JPanel(BorderLayout()) from java.awt import FlowLayout p=JPanel(FlowLayout(FlowLayout.LEFT)) from javax.swing import JButton button=JButton("Choose SVG File") p.add(button) from org.apache.batik.swing import JSVGCanvas svgCanvas=JSVGCanvas() panel.add("North", p) panel.add("Center", svgCanvas) def buttonActionPerformed(ae): from javax.swing import JFileChooser fc=JFileChooser(".") choice=fc.showOpenDialog(panel) if choice==JFileChooser.APPROVE_OPTION: f=fc.selectedFile svgCanvas.setURI(f.toURL().toString()) button.actionPerformed=buttonActionPerformed from javax.swing import JFrame from java.awt import Rectangle frame=JFrame( title="Batik Test", bounds=Rectangle(800, 600), visible=False ) frame.contentPane.add(panel) def windowClosing(e): window=e.window window.visible=False window.dispose() frame.windowClosing=windowClosing frame.visible=True if __name__=="__main__": from java.awt import EventQueue EventQueue.invokeLater(PyCallableRunnable(initializeAWTUI))
環境変数 CLASSPATH に ./batik.jar を追記して、
jython svgviewer.py
なんかだらだら解説を書くのに疲れたので、各自解読してください。
I/O に悩む(2)
I/O で悩む
ディスクアクセスの効率化についていろいろ調べたりしているのですが、複雑すぎてよく分からないというのが正直な感想です。
Windows の Cached I/O と Non-Cached I/O
http://d.hatena.ne.jp/NyaRuRu/20080228/p1
http://d.hatena.ne.jp/NyaRuRu/20080229/p1
http://d.hatena.ne.jp/NyaRuRu/20080306/p1
http://d.hatena.ne.jp/NyaRuRu/20080310/p1
解決策
下に行くほど根本的だが非現実的。
- 今使っているマシンの搭載メモリ量が32bitの壁に達していないならば、搭載メモリを増やす。
- 64bit化して搭載メモリを増やす。
- 今使っているハードディスクの容量に匹敵する容量ののRAMディスクを用意する。
いや、全部非現実的かな……
単にメモリは多ければ多いほど良いですよ、ってだけですね。
構想
64bit環境で数百GBのイメージファイルを丸ごとメモリにマップ。
→そのメモリ上にヒープを構築すれば、比較的単純なコードでファイルシステムを作れる。
→デバイスに直接アクセスするコードを書くのは怖いけれど、これなら野心的なファイルシステムをどんどん自作可能。
既存のインターフェースから使うにはFUSE http://fuse.sourceforge.net/ やDokan http://dokan-dev.net/ でユーザーモードファイルシステムを作りイメージファイルをマウントできるようにする。
ドライバ開発はハードウェアに近いので怖い。
ちなみに完成しても無茶苦茶俺専用仕様になるでしょうから、公開も紹介もしないでしょう。
Jython を学ぶシリーズはしばらく中断
コードを書く精神的・時間的・知的余裕がなくなってきました。プログラミングが専門じゃない、というか専門は文系分野なのです。
Jython を学ぶ(4)───Jython で Java の配列を使う(2)、 Jython で Java の I/O 関連 API を使う(1)
Jython を学ぶ(4) part1───Jython で Java の配列を使う(2)
前回の続きです。
例を再掲します。
#!jython import jarray buffer=jarray.zeros(3, "b") for i in range(0, len(buffer)): buffer[i]=i for b in buffer: print b buffer1=jarray.zeros(5, "b") from java.lang import System System.arraycopy(buffer, 0, buffer1, 0, 3) print buffer1 pyList=[5, 4, 3, 2, 1] buffer2=jarray.array(pyList, "b") print buffer2
この Jython コードにおおよそ相当する Java のコードは以下の通りです。
class ArrayTest{ public static void main(String args[]){ byte[] buffer=new byte[3]; for(byte i=0; i<buffer.length; i++){ buffer[i]=i; } for(byte b:buffer){ System.out.println(b); } byte[] buffer1=new byte[5]; System.arraycopy(buffer, 0, buffer1, 0, 3); for(byte b:buffer1){ System.out.print(b); } System.out.print("\n"); byte[] buffer2={5, 4, 3, 2, 1}; for(byte b:buffer2){ System.out.print(b); } System.out.print("\n"); } }
javac ArrayTest.java
java ArrayTest
この例の要点は以下の通りです。
jarray.zeros 関数
zeros 関数は第一引数に長さ、第二引数に配列に格納される値型をあらわす文字または格納される参照の型をとり、そのような配列をゼロフィルして返します。
値型を表す文字は Python 標準ライブラリの array モジュールで使われている文字と同じで、以下の通りです。
文字 | 型 |
---|---|
"z" | boolean |
"c" | char |
"b" | byte |
"h" | short |
"i" | int |
"l" | long |
"f" | float |
"d" | double |
jarray.array 関数
array 関数は第一引数に Python のシーケンス、第二引数に zeros と同様の型の指定をとり、シーケンスの内容が格納されたシーケンスと同じ長さの配列を返します。
java.lang.System#arraycopy メソッド
System#arraycopy メソッドは配列を効率的にコピーする静的メソッドです。
http://java.sun.com/javase/ja/6/docs/ja/api/java/lang/System.html#arraycopy(java.lang.Object, int, java.lang.Object, int, int)
arraycopy は Java の for 文でコピーする場合はもちろん、 JNI を使ってコピーするよりはるかに高速です。これは arraycopy が JVM に組み込まれているからです。
arraycopy を使えば、( Java プラットフォームが正しく実装されていれば)型安全に高速な配列のコピーができます。
Jython を学ぶ(4) part2───Jython で Java の I/O 関連 API を使う(1)
Java の配列を扱えるので、バイト配列を多用する Java の I/O を Jython だけでも効率よく使うことができます。
本当は Java7 で導入されるNIO2(JSR 203: More New I/O APIs for the JavaTM Platform ("NIO.2"), http://www.jcp.org/en/jsr/detail?id=203)を使いたいのですが、 Java7 のリリース予定は2009年の夏です。
正式リリース以前のライブラリを使ったコードは例として良くないので、Early Access 版は使いません。
NIO2 では OS の非同期 I/O 機能を使う API が提供されるようです。この機能を使えば OS によって最適化された効率の良い非同期 I/O が実現できるでしょう。
しかし今はまだこれを使えませんので、非常に単純な非同期 I/O 機能を Jython で書くことにします。
以下はカレントディレクトリの source という名前のファイルの内容を destination という名前のファイルにコピーする Jython スクリプトです。
ファイルが存在しなかったりすると例外を送出して停止します。
#!jython import jarray from java.lang import Runnable from java.lang import ThreadLocal from java.io import FileInputStream from java.io import FileOutputStream from java.util.concurrent import Callable from java.util.concurrent import Executors from java.util.concurrent import FutureTask class PyCallableRunnable(Runnable): def __init__(self, pyCallable): self.pyCallable=pyCallable return def run(self): self.pyCallable() return class PyCallableCallable(Callable): def __init__(self, pyCallable): self.pyCallable=pyCallable return def call(self): return self.pyCallable() asyncTranslateThreadLocal=ThreadLocal() asyncTranslateExecutorService=Executors.newSingleThreadExecutor() #newCachedThreadPool(), newFixedThreadPool(n), ... def asyncTranslate(inputStream, outputStream): def translate(): amountTranslated=0 buffer=asyncTranslateThreadLocal.get() if buffer is None: buffer=jarray.zeros(4048, "b") asyncTranslateThreadLocal.set(buffer) while True: amountRead=inputStream.read(buffer) if amountRead==-1: break amountTranslated+=amountRead outputStream.write(buffer, 0, amountRead) return amountTranslated futureTask=FutureTask(PyCallableCallable(translate)) asyncTranslateExecutorService.execute(futureTask) return futureTask def asyncCopy(sourcePath, destinationPath): inputStream=FileInputStream(sourcePath) outputStream=FileOutputStream(destinationPath) future=asyncTranslate(inputStream, outputStream) return future if __name__=="__main__": future=asyncCopy("source", "destination") print future.get() asyncTranslateExecutorService.shutdown()
この場合は非同期である意味がほとんどありませんが、複雑なプログラムでは非同期 I/O が役に立つでしょう。
内容の解説は次回に回します。
Jython を学ぶ(3)───Jython で Java の配列を使う(1)
Apache Batik を使う例を書いていたら、色々と解決すべき問題があったため、まずはそちらについて書くことにします。
Jython で Java の配列を使う
Java の配列は固定長配列です。
値型(boolean, char, byte, short, int, long, float, double)を扱う Java の API を呼び出したときは、それに相当するクラスのインスタンス(Boolean, Char, Byte, Short, Integer, Long, Float, Double)との間で Boxing ・ Unboxsing が行われます。
しかし長大な配列を扱うには Java の固定長配列を直接扱えた方がいいので、 Jython では Java の固定長配列を扱うためのモジュール jarray が提供されています。
http://www.jython.org/docs/jarray.html
Python の標準ライブラリでも値型配列を扱うための array モジュールが提供されています。
http://docs.python.org/lib/module-array.html
これは Jython でも提供されています。
Numerical Python と呼ばれるライブラリではさらに効率的な配列が使えます。
jarray の簡単な使い方の例は以下の通りです。
#!jython import jarray buffer=jarray.zeros(3, "b") for i in range(0, len(buffer)): buffer[i]=i for b in buffer: print b buffer1=jarray.zeros(5, "b") from java.lang import System System.arraycopy(buffer, 0, buffer1, 0, 3) print buffer1 pyList=[5, 4, 3, 2, 1] buffer2=jarray.array(pyList, "b") print buffer2
結果は
0 1 2 array('b',[0, 1, 2, 0, 0]) array('b',[5, 4, 3, 2, 1])
どうにも時間がないので
例の解説はまた次回