Jython を学ぶ(4)───Jython で Java の配列を使う(2)、 Jython で Java の I/O 関連 API を使う(1)

Jython を学ぶ(4) part1───JythonJava の配列を使う(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 の配列を Python のシーケンスとして使うことができます。例えば for 文、 len 関数、 range 関数と共に使うことができます。

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───JythonJava の 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 が役に立つでしょう。
内容の解説は次回に回します。