7
ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016 37 //new to java / J ava 7で新しいI/O APIが導入されました。このAPIは通常、NIO.2と呼ば れるもので、元々のFileによるI/O操作はほぼ完全にNIO.2に取って代わ られると考えて差し支えないでしょう。新しいクラスはjava.nio.fileパッケー ジに含まれています。 新しいAPIは多くのユースケースで使い勝手が大幅に向上しています。こ のAPIは大きく2つの要素に分けられます。1つ目は、 Path と呼ばれる新しい 抽象化です(ファイルの場所を表すものと考えることができますが、ファイ ルがそこに実際にあってもなくても構いません)。2つ目は、ファイルやファ イル・システムを処理するための多数の新しいコンビニエンス・メソッドやユ ーティリティ・メソッドです。これらのメソッドは、静的メソッドとしてFiles ラスに含まれています。 たとえば、新しいFiles機能を使用した場合、基本的なコピー操作は次の ように簡単になります。 File inputFile = new File("input.txt"); try (InputStream in = new FileInputStream(inputFile)) { Files.copy(in, Paths.get("output.txt")); } catch(IOException ex) { ex.printStackTrace(); } それでは、 Filesのおもなメソッドの一部を簡単に見ていきます。ほとんど のメソッドは、説明しなくても何をするものかすぐにわかります。多くの場 合、メソッドには戻り値の型がありますが、ここではその処理を省略してい ます。というのも、戻り値の処理を目的にした例や、同等のCコードの動作 を再現する場合以外はほとんど役に立たないからです。 Path source, target; Attributes attr; Charset cs = StandardCharsets.UTF_8; // ファイルの作成 // // パスの例 --> /home/ben/.profile // 属性の例s --> rw-rw-rw- Files.createFile(target, attr); // ファイルの削除 Files.delete(target); boolean deleted = Files.deleteIfExists(target); // ファイルのコピー/移動 Files.copy(source, target); Files.move(source, target); // 情報を取得するためのユーティリティ・メソッド long size = Files.size(target); FileTime fTime = Files.getLastModifiedTime(target); System.out.println(fTime.to(TimeUnit.SECONDS)); Map<String, ?> attrs = Files.readAttributes(target, "*"); System.out.println(attrs); // ファイル・タイプを処理するメソッド BEN EVANS PHOTOGRAPH BY JOHN BLYTHE 最新のJava I/O ディレクトリの変更の監視など、さまざまな処理がNIO.2で簡単に BENJAMIN EVAN DAVID FLANAGAN

最新のJava I/O - Oracle · Java 1.4で初めてJava New I/O(NIO)APIというソリューションが追加されましたが、このNIOが後続の Javaバージョンで引き続き幅を利かせてきました。

  • Upload
    others

  • View
    2

  • Download
    0

Embed Size (px)

Citation preview

Page 1: 最新のJava I/O - Oracle · Java 1.4で初めてJava New I/O(NIO)APIというソリューションが追加されましたが、このNIOが後続の Javaバージョンで引き続き幅を利かせてきました。

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

37

//new to java /

Java 7で新しいI/O APIが導入されました。このAPIは通常、NIO.2と呼ばれるもので、元々のFileによるI/O操作はほぼ完全にNIO.2に取って代わ

られると考えて差し支えないでしょう。新しいクラスはjava.nio.fileパッケージに含まれています。

新しいAPIは多くのユースケースで使い勝手が大幅に向上しています。このAPIは大きく2つの要素に分けられます。1つ目は、Pathと呼ばれる新しい抽象化です(ファイルの場所を表すものと考えることができますが、ファイルがそこに実際にあってもなくても構いません)。2つ目は、ファイルやファイル・システムを処理するための多数の新しいコンビニエンス・メソッドやユーティリティ・メソッドです。これらのメソッドは、静的メソッドとしてFilesクラスに含まれています。

たとえば、新しいFiles機能を使用した場合、基本的なコピー操作は次のように簡単になります。

File inputFile = new File("input.txt");try (InputStream in = new FileInputStream(inputFile)) { Files.copy(in, Paths.get("output.txt"));} catch(IOException ex) { ex.printStackTrace();}

それでは、Filesのおもなメソッドの一部を簡単に見ていきます。ほとんどのメソッドは、説明しなくても何をするものかすぐにわかります。多くの場合、メソッドには戻り値の型がありますが、ここではその処理を省略しています。というのも、戻り値の処理を目的にした例や、同等のCコードの動作を再現する場合以外はほとんど役に立たないからです。

Path source, target;

Attributes attr;Charset cs = StandardCharsets.UTF_8;

// ファイルの作成//// パスの例 --> /home/ben/.profile// 属性の例s --> rw-rw-rw-Files.createFile(target, attr);

// ファイルの削除Files.delete(target);boolean deleted = Files.deleteIfExists(target);

// ファイルのコピー/移動Files.copy(source, target);Files.move(source, target);

// 情報を取得するためのユーティリティ・メソッドlong size = Files.size(target);

FileTime fTime = Files.getLastModifiedTime(target);System.out.println(fTime.to(TimeUnit.SECONDS));

Map<String, ?> attrs = Files.readAttributes(target, "*");System.out.println(attrs);

// ファイル・タイプを処理するメソッドBEN EVANS PHOTOGRAPH BY JOHN BLYTHE

最新のJava I/Oディレクトリの変更の監視など、さまざまな処理がNIO.2で簡単に

BENJAMIN EVAN DAVID FLANAGAN

Page 2: 最新のJava I/O - Oracle · Java 1.4で初めてJava New I/O(NIO)APIというソリューションが追加されましたが、このNIOが後続の Javaバージョンで引き続き幅を利かせてきました。

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

38

//new to java /

boolean isDir = Files.isDirectory(target);boolean isSym = Files.isSymbolicLink(target);

// 読取り/書込みを処理するメソッドList<String> lines = Files.readAllLines(target, cs);byte[] b = Files.readAllBytes(target);

BufferedReader br = Files.newBufferedReader(target, cs);BufferedWriter bwr = Files.newBufferedWriter(target, cs);

InputStream is = Files.newInputStream(target);OutputStream os = Files.newOutputStream(target);

Filesのメソッドの中には、オプションの引数を渡して操作に動作(実装固有の可能性もあり)を追加できるものもあります。

ここで選んだAPIの中には、悩ましい動作を引き起こすものもあります。たとえば、デフォルトのコピー操作では既存のファイルが上書きされないため、上書き動作をコピー・オプションとして次のように指定する必要があります。

Files.copy(Paths.get("input.txt"), Paths.get("output.txt"), StandardCopyOption.REPLACE_EXISTING);

StandardCopyOptionは、CopyOptionと呼ばれるインタフェースを実装する列挙型です。このインタフェースはLinkOptionでも実装できます。したがって、Files.copy()にはLinkOption引数とStandardCopyOption引数のいずれかを任意の数指定することができます。LinkOptionはシンボリック・リンクの処理方法を指定するときに使用します(当然ながら、基盤となるオペレーティング・システムがシンボリック・リンクをサポートしていることが前提です)。

PathPathは、ファイル・システム内にあるファイルを探すときに使用できる型で、次の特徴を持つパスを表します。

■■ システム依存■■ 階層■■ パス要素のシーケンスで構成■■ 仮想(まだ存在していなかったり、すでに削除されたりしていても構わない)そのため、Fileとは根本的に異なりま

す。特に、Pathはクラスでなくインタフェースであることから、システム依存であることは明らかです。そのため、ファイル・システム・プロバイダごとにそれぞれ独自のPathインタフェースを実装し、システム固有の機能を提供しても、全体的な抽象化は維持することができます。

Pathの要素にオプションとしてルート・コンポーネントを含めることもでき、これにより、そのインスタンスが属しているファイル・システムの階層がはっきりします。なお、たとえば、相対Pathインスタンスにはルート・コンポーネントがないこともあります。すべてのPathインスタンスには、ルートの他にゼロ個以上のディレクトリ名と1つの名前要素が含まれます。

名前要素はディレクトリ階層のルートからもっとも遠い要素で、ファイルまたはディレクトリの名前を表します。Pathは、特殊なセパレータまたはデリミタで結合されたパス要素で構成されていると考えることができます。

Pathは抽象的な概念で、必ずしも物理的なファイル・パスに紐付けられている必要はありません。そのため、まだ存在していないファイルの場所を簡単に指定できます。JavaにはPathsクラスが付属しており、Pathインスタンスを作成するためのファクトリ・メソッドはこのクラスにあります。

Pathsには、Pathオブジェクトを作成するためのget() メソッドが2つあります。通常のバージョンは文字列を取り、デフォルトのファイル・システム・プロバイダを使用します。URIバージョンではNIO.2の機能を利用し、独自のファイル・システムのプロバイダを追加で組み込みます。これは高度な用法であるため、興味のある方は一次文献を参照してください。

Path p = Paths.get("/Users/ben/cluster.txt");Path p =

Pathは抽象的な概念です。必ずしも物理的なフ

ァイル・パスに紐付けら

れている必要はないた

め、まだ存在していない

ファイルの場所を簡単

に指定できます。

Page 3: 最新のJava I/O - Oracle · Java 1.4で初めてJava New I/O(NIO)APIというソリューションが追加されましたが、このNIOが後続の Javaバージョンで引き続き幅を利かせてきました。

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

39

//new to java /

Paths.get(new URI( "file:///Users/ben/cluster.txt"));System.out.println(p2.equals(p));

File f = p.toFile();System.out.println(f.isDirectory());Path p3 = f.toPath();System.out.println(p3.equals(p));

この例は、PathオブジェクトとFileオブジェクトの相互運用が簡単なことも示しています。toFile()メソッドをPathに追加し、toPath()メソッドをFileに追加すると、2つのAPI間を難なく移動できるほか、Fileに基づくコードの内容を単純な方法でリファクタリングして代わりにPathを使用するようにできます。

Filesクラスには“橋渡しをする”便利なメソッドもいくつかあり、それらのメソッドを利用することもできます。それらのメソッドを利用することで、例えば、指定されたPathの位置にWriterオブジェクトを開くコンビニエンス・メソッドを指定して、古いI/O APIに簡単にアクセスできます。

Path logFile = Paths.get("/tmp/app.log");try (BufferedWriter writer = Files.newBufferedWriter( logFile, StandardCharsets.UTF_8, StandardOpenOption.WRITE)) { writer.write("Hello World!"); // ...} catch (IOException e) { // ...}この例では、StandardOpenOption列挙型を使用しています。コピー・オプションに似ていますが、ファイルのコピーではなく、新しいファイルを開くための機能です。

次の例では、JARファイルをFileSystemそのものとして操作し、さらなるファイルを直接JARに追加するように変更しています。JARファイルはZIPファイルにすぎないため、この手法は.zipアーカイブにも使用できます。

Path tempJar = Paths.get("sample.jar");try (FileSystem workingFS = FileSystems.newFileSystem(tempJar, null)) { Path pathForFile = workingFS.getPath("/hello.txt"); List<String> ls = new ArrayList<>(); ls.add("Hello World!");

Files.write(pathForFile, ls, Charset.defaultCharset(), StandardOpenOption.WRITE, StandardOpenOption.CREATE);}

この例は、FileSystemを使用し、getPathメソッドを介してファイル・システム内にPathオブジェクトを作成する方法を示しています。こうすることで、FileSystemオブジェクトを実質的にブラック・ボックスとして扱うことができます。

Javaの元々のI/O APIに対する批判の1つは、ネイティブで高パフォーマンスのI/Oがサポートされないことでした。Java 1.4で初めてJava New I/O(NIO)APIというソリューションが追加されましたが、このNIOが後続のJavaバージョンで引き続き幅を利かせてきました。

NIOのチャネルとバッファNIOのバッファは高パフォーマンスのI/Oを低レベルで抽象化したもので、特定のプリミティブ型要素のリニアなシーケンスに配列するためのコンテナの役割を果たします。例として、ByteBuffer(もっとも一般的なケース)を取り上げます。ByteBuffer:これはバイトのシーケンスで、概念上は、byte[]による操作に対するパフォーマンス重視の代替手段と考えることができます。可能な範囲で最高のパフォーマンスが得られるように、ByteBufferでは、JVMが稼働しているプラットフォームのネイティブ機能を直接操作できます。

このアプローチは”ダイレクト・バッファ”ケースと呼ばれ、可能な場合は必ずJavaのヒープがバイパスされます。ダイレクト・バッファは標準Javaヒープではなくネイティブ・メモリに割り当てられ、通常のヒープ上のJavaオブジェ

Page 4: 最新のJava I/O - Oracle · Java 1.4で初めてJava New I/O(NIO)APIというソリューションが追加されましたが、このNIOが後続の Javaバージョンで引き続き幅を利かせてきました。

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

40

//new to java /

クトとは異なりガベージ・コレクションの対象にはなりません。ダイレクト・バッファであるByteBufferを取得するためには、

allocateDirect()ファクトリ・メソッドをコールします。 ヒープ上のオブジェクトを対象にしたallocate()もありますが、実際はあまり使用されません。

バイト・バッファを取得する3つ目の方法は、既存のbyte[]をラップするやり方です。この方法を使用するとヒープ上にバッファが取得され、元のバイト配列をよりオブジェクト指向らしい扱いができるようになります。

ByteBuffer b = ByteBuffer.allocateDirect(65536);ByteBuffer b2 = ByteBuffer.allocate(4096);

byte[] data = {1, 2, 3};ByteBuffer b3 = ByteBuffer.wrap(data);

バイト・バッファは、バイトに低レベルでアクセスするためのものです。つまり、バイトのエンディアンや、Javaの汎整数型プリミティブでの符号の有無を処理する必要性など、細かい部分は手動で処理する必要があるということです。

b.order(ByteOrder.BIG_ENDIAN);

int capacity = b.capacity();int position = b.position();int limit = b.limit();int remaining = b.remaining();boolean more = b.hasRemaining();

バッファにデータを取り込む操作とバッファからデータを取り出す操作の種類は、単一の値の読取り/書込みを行う単一値方式と、byte[]またはByteBufferを受け取り、いくつかの値(場合によっては多数の値)に対して単一操作を行うバルク方式の2つがあります。パフォーマンスの向上が期待できるのはバルク操作です。

b.put((byte)42);b.putChar('x');

b.putInt(0xcafebabe);

b.put(data);b.put(b2);

double d = b.getDouble();b.get(data, 0, data.length);

単一値方式の場合は、バッファ内の絶対位置も指定できます。

b.put(0, (byte)9);

バッファはメモリ内の抽象化です。外部(ファイルやネットワークなど)に影響を与えるには、パッケージjava.nio.channelsに含まれるChannelを使用する必要があります。チャネルとは、読取り/書込み操作をサポートできるエンティティへの接続です。チャネルの例として一般的なのはファイルとソケットですが、待機時間の短いデータ処理に使用されるカスタム実装を考えることもできます。

チャネルは作成時に開かれ、後から閉じることができます。いったん閉じたチャネルを再度開くことはできません。チャネルは通常、読取り可能または書込み可能のいずれかであり、両方ではありません。チャネルを理解するうえで重要なのは、チャネルを読み取るとバッファにバイトが書き込まれ、チャネルに書き込むとバッファからバイトが読み取られる点です。たとえば、大きいファイルのチェックサムを16MBのチャンク単位で計算する必要があるとします。FileInputStream fis = getSomeStream();boolean fileOK = true;

try (FileChannel fchan = fis.getChannel()) { ByteBuffer buffy = ByteBuffer.allocateDirect(16 * 1024 * 1024); while(fchan.read(buffy) != -1 || buffy.position() > 0 || fileOK) { fileOK = computeChecksum(buffy); buffy.compact();

Page 5: 最新のJava I/O - Oracle · Java 1.4で初めてJava New I/O(NIO)APIというソリューションが追加されましたが、このNIOが後続の Javaバージョンで引き続き幅を利かせてきました。

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

41

//new to java /

}} catch (IOException e) { System.out.println("Exception in I/O");}

このコードでは、可能な限りネイティブI/Oを使用し、Javaのヒープ内外のバイトが大量にコピーされないようにしています。computeChecksumメソッドが適切に実装されていれば、これは非常にパフォーマンスに優れた実装だと言えるでしょう。マップされたバイト・バッファ: これはダイレクト・バイト・バッファの一種で、ここにはメモリにマップされたファイル(またはその一部分)が含まれます。このバッファはFileChannelオブジェクトから作成されますが、MappedByteBufferに対応するFileオブジェクトはメモリ・マッピング操作の前に使用する必要があります。そうしないと、例外がスローされます。例外にならないようにするために、ここでもtry-with-resourcesを使用し、対象となるオブジェクトを絞り込みます。

try (RandomAccessFile raf = new RandomAccessFile( new File("input.txt"), "rw"); FileChannel fc = raf.getChannel();) {

MappedByteBuffer mbf = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size()); byte[] b = new byte[(int)fc.size()]; mbf.get(b, 0, b.length); for (int i=0; i<fc.size(); i++) { b[i] = 0; // Won't be written back to the // file, we're a copy } mbf.position(0); mbf.put(b); // Zeroes the file}

E大量のI/O操作(ファイル・システム間で10GBを送信するなど)が単一スレッドで同時に実行される場合は、バッファを使用してもJavaでできることには限りがあります。Java 7より前は、通常、独自のマルチスレッド・コードを記述し、独立したスレッドでバックグラウンド・コピーが実行されるようにすることで、この種の操作を実行していました。それでは次に、JDK 7で追加された新しい非同期I/O機能を見てみます。

非同期I/O新しい非同期機能のポイントは、Channelに追加されたいくつかの新しいサブクラスです。バックグラウンド・スレッドに引き継ぐ必要があるI/O操作を、これらのサブクラスで処理できます。この同じ機能は、大量の長時間実行の操作や、他のいくつかのユースケースに適用できます。

このセクションで説明するのはファイルI/Oを処理するAsynchronous FileChannelだけですが、他にもいくつかの非同期チャネルがあります。非同期チャネルの操作方法には、Future方式とコールバック方式の2種類があります。Futureベース方式:Futureインタフェースは本記事の対象外ですが、このインタフェースは、完了している場合も完了していない場合もある継続タスクと考えることができます。重要なメソッドは、タスクが終了しているかどうかを示すブール値を返すisDone()と、結果を返すget()の2つです。タスクが終了していれば結果が直ちに返され、そうでなければ終了するまでブロックされます。

次に示すのは、大きいファイル(およそ100MB)を非同期式で読み込むプログラムの例です。

try (AsynchronousFileChannel channel = AsynchronousFileChannel.open( Paths.get("input.txt"))) { ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 100); Future<Integer> result = channel.read(buffer, 0);

while(!result.isDone()) { // Do some other useful work.... }

Page 6: 最新のJava I/O - Oracle · Java 1.4で初めてJava New I/O(NIO)APIというソリューションが追加されましたが、このNIOが後続の Javaバージョンで引き続き幅を利かせてきました。

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

42

//new to java /

System.out.println("Bytes read: " + result.get());}

コールバックベース方式:コールバック方式による非同期I/OではCompletionHandlerを使用します。CompletionHandlerには2つのメソッド、completed()とfailed()が定義されていて、操作が成功または失敗したときにコールバックされます。

この方式は、非同期I/Oのイベント通知をすぐに必要とする場合に便利です。たとえば、大量のI/O操作がある場合に、どれか1つの操作が失敗しても必ずしも致命的ではないような場合です。

byte[] data = {2, 3, 5, 7, 11, 13, 17, 19, 23};ByteBuffer buffy = ByteBuffer.wrap(data);

CompletionHandler<Integer,Object> h = new CompletionHandler() { public void completed(Integer written, Object o) { System.out.println("Bytes written: " + written); }

public void failed(Throwable x, Object o) { System.out.println( "Async write failed: "+ x.getMessage()); }};

try (AsynchronousFileChannel channel = AsynchronousFileChannel.open( Paths.get("primes.txt"), StandardOpenOption.CREATE, StandardOpenOption.WRITE)) {

channel.write(buffy, 0, null, h); Thread.sleep(1000); // すぐには終了しません}

AsynchronousFileChannelオブジェクトはバックグラウンドのスレッド・プールに関連付けられているため、I/O操作を続行しながら、元のスレッドで他のタスクの処理を進めることができます。

この場合、デフォルトでは、ランタイムによって提供されるマネージド・スレッド・プールが使用されます。必要であれば、アプリケーションで管理されるスレッド・プールを(AsynchronousFileChannel.open()のオーバーロードされた形式を介して)使用するようにこのオブジェクトを作成することもできますが、必要な場面はあまりありません。

最後に、まだ説明していなかったI/O多重化のサポートに触れておきます。I/Oを多重化すると、複数のチャネルを単一のスレッドで管理でき、どのチャネルで読取りまたは書込みが可能かを調査できます。これをサポートするためのクラスはjava.nio.channelsパッケージにあり、SelectableChannelとSelectorが含まれます。

ノンブロッキングの多重化テクニックは、優れたスケーラビリティを必要とする高度なアプリケーションを記述するときに非常に便利ですが、本記事の趣旨から外れているため詳しくは説明しません。.

監視サービスとディレクトリ検索非同期サービスのクラスの最後として、ディレクトリの監視やディレクトリ(またはツリー)のアクセスについて見ていきます。監視サービスを動作させるには、ディレクトリ内での出来事(ファイルの作成や変更など)をすべて観察します。

try { WatchService watcher = FileSystems.getDefault().newWatchService();

Path dir = FileSystems.getDefault().getPath("/home/ben"); WatchKey key = dir.register(watcher, StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_DELETE);

Page 7: 最新のJava I/O - Oracle · Java 1.4で初めてJava New I/O(NIO)APIというソリューションが追加されましたが、このNIOが後続の Javaバージョンで引き続き幅を利かせてきました。

ORACLE.COM/JAVAMAGAZINE /////////////////////////////// JULY/AUGUST 2016

43

//new to java /

while(!shutdown) { key = watcher.take(); for (WatchEvent<?> event: key.pollEvents()) { Object o = event.context(); if (o instanceof Path) { System.out.println("Path altered: "+ o); } } key.reset(); }}

これに対し、ディレクトリ・ストリームを使用すると、現在1つのディレクトリの中にあるすべてのファイルを走査できます。たとえば、すべてのJavaソース・ファイルとそのサイズ(バイト単位)をリストする場合は、次のようなコードを使用できます。

try(DirectoryStream<Path> stream = Files.newDirectoryStream( Paths.get("/opt/projects"), "*.java")) { for (Path p : stream) { System.out.println(p +": "+ Files.size(p)); }}

このAPIの欠点の1つは、グロブ構文に基づいた一致要素しか返さないことです。グロブ構文は柔軟性に欠ける場合があるのです。新しいFiles.findメソッドとFiles.walkメソッドを使用し、ディレクトリの再帰的な走査によって取得された各要素を処理すれば、詳細に検索できます。

final Pattern isJava = Pattern.compile(".*\\.java$");final Path homeDir = Paths.get("/Users/ben/projects/");Files.find(homeDir, 255, (p, attrs) -> isJava.matcher(p.toString()).find()) .forEach( q -> {System.out.println(q.normalize());});

さらに踏み込んで、java.nio.fileに含まれるFileVisitorインタフェースを使用する高度なソリューションを構築することもできます。ただし、そのためには、ここで行ったように1つのラムダ式を使用するのではなく、このインタフェースの4つのメソッドをすべて実装する必要があります。

ここまで見てきたところで、NIO.2ライブラリには便利な機能が多くあり、大量のコードを書かなくても済むことがわかったと思います。Java 7より前のファイル操作をいまだに使用しているのだとしたら、必要量をはるかに超える作業をしていることになります。</article>

本記事は、承諾を得たうえで『Java in a Nutshell』(共著者:Benjamin Evans、David Flanagan)の内容を本誌向けに再編集したものです。

Benjamin Evans:jClarityの共同設立者。Java ChampionとRock Starの称号を持ち、Java Magazineにたびたび寄稿している。 David Flanagan:Mozillaのソフトウェア・エンジニア。名著『JavaScript: the Definitive Guide』(O’Reilly、2011年)で有名。