JavaでI / Oパッケージを実装するというアイデア



追加するものが何もない場合、完全性は達成されません。

そして何も奪うことができないとき。

アントワーヌ・ド・サン=テグジュペリ、風、砂、星、1939



多くの場合、Javaアプリケーション用のI / Oパッケージを設計および開発する必要があります。 一方ではjava.ioがありますが、これは十分すぎるほどです。 ただし、実際には、一連の標準クラスとインターフェースを使用することはほとんど不可能です。



この記事は、JavaプラットフォームでI / Oパッケージを実装するためのアイデアの実用的な例を提供します。





問題の声明



明確にするために、例を考えてみましょう。 マトリックスライブラリのI / Oパッケージを開発するとします。 この場合、次のことに留意する必要があります。



MatrixMarketフォーマット



MatrixMarket形式の場合、次のマトリックス表現があります。

0 2 3 0
      
      





密行列の場合:



 %%MatrixMarket matrix array real general 2 2 0 2 3 0
      
      





スパース行列の場合:



 %%MatrixMarket matrix coordinate real general 2 2 2 0 1 2 1 0 3
      
      





実装



したがって、I / Oパッケージの実装は非常に柔軟でなければならないため、システムを拡張するとき(たとえば、ブロックなどの新しいタイプのマトリックスを追加したり、CSVなどの新しいフォーマットを追加したり)、パッケージを完全に書き換える必要はありませんでしたが、追加のクラスを実装するだけで十分でした-新しいクラスマトリックスまたは新しい形式。



注意深い読者は、 ブリッジテンプレートによって解決された問題と説明された問題の明確な類似性に気付くでしょう。 これは事実であり、この記事はI / Oパッケージ用のBridgeテンプレートの実装の例として取り上げることができます。



設計パターンの基本戻って、抽象化と実装の分離としてブリッジパターンを簡単に説明できます。 私たちの場合、フォーマット(XML、MatrixMarket)からのマトリックスのタイプ(高密度、ブロック)の分離。 これは、抽象化インターフェースと実装インターフェースという2つのインターフェースの導入により実現されます。 抽象化インターフェースは、readMatrix()、writeMatrix()メソッドなど、パッケージの動作を高レベルで記述する必要があります。 同時に、実装インターフェースは、readMatrixElement()、writeMatrixElement()などの低レベルの問題を記述する必要があります。 次に、最も単純なケースでは、I / Oパッケージのクラス図は次のようになります。







高レベルのwriteMatrix()メソッドは、低レベルの呼び出しのシーケンスです。



ブリッジテンプレートは、実装と抽象化の分離のおかげで、前述の問題を解決することがわかりました。 しかし、ほとんどの場合、I / Oパケットが動作するオブジェクトは既にシリアル化メカニズム(Srializable、Externalizable)を実装しています。 この場合、MatrixインターフェースはすでにExternalizableインターフェースを拡張しています。 Serizliableではなく、Externalizableが正確に、なぜこの研究またはこの(著者の研究研究で読めるのか。 要するに、Externalizbleは、JVM / Reflectionでの呼び出し回数が少ないため、何倍も速く動作します。



したがって、密行列のreadExternal / writeExternalメソッドは次のようになります。



 public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(rows); out.writeInt(columns); for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { out.writeDouble(self[i][j]); } } } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { rows = in.readInt(); columns = in.readInt(); self = new double[rows][columns]; for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { self[i][j] = in.readDouble(); } } }
      
      





注意深い読者は「これはBridgeテンプレートとまったく同じです!」と言うでしょう。 ObjectOutputおよびObjectInputインターフェースは、実装インターフェースとしてテンプレートの概念を実装します。 次に疑問が生じます-「MatrixReader / MarixWriterのようなクラスをさらに作成し、それらに重複するreadExterna()l / writeExternal()メソッドを記述する理由」。 そうです-理由はありません。 さらに、DRY(Do n’t Repeat Yourself)方法論はこれを思い出させます。



この場合、java.ioにすでに実装インターフェースObjectInput / ObjectOutputが含まれているという事実を考慮して、パッケージの提案された実装の修正を試みます。 つまり フォーマットクラス-MMOutoutStream / MMInputStream(MM = MatrixMarket)を実装するだけで、シリアル化の標準クラス-ObjectInputStream / ObjectOutputStreamの代わりに使用できます。 その後、使用は非常に透明になります。



 //  ObjectOutput mmos = new MMOutputStream(“file.mm”); mmos.writeObject(a); mmos.close(); //  ObjectInput mmis = new MMInputStream(“file.mm”); Matrix b = (Matrix) mmis.readObject();
      
      





上記のコードは、シリアル化コードに簡単に変換できます。 これを行うには、MM *クラスをObject *に置き換えます。 (MMOutputStream-> ObjectOutputStream)。



未解決の問題が1つあります。 ファイルの論理ブロックの分離の問題。 この場合、ファイルは次のように分割されます。



パッケージの以前のアーキテクチャでは、この情報を個別に記録できる分離された方法が提示されていました。 ただし、ObjectOutput / ObjectInputインターフェースには明らかにそのようなメソッドは含まれていません。 つまり 標準クラスのメソッドは下位レベルです。



この問題を解決するために、著者は、ブロック(ヘッダー(HEADER_MARKER)、メタ情報(META_MARKER)、および要素(ELEMENT_MARKER))のそれぞれの境界を示す特別なマーカー(バイト)を使用することを提案します。



次に、writeExternal()/ readExternal()メソッドは次のようになります。

 @Override public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(rows); out.writeInt(columns); out.writeByte(META_MARKER); //   META for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { out.writeDouble(self[i][j]); out.writeByte(ELEMENT_MARKER); //   ELEMENT } } } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { rows = in.readInt(); columns = in.readInt(); in.readByte(); //   META self = new double[rows][columns]; for (int i = 0; i < rows; i++) { for (int j = 0; j < columns; j++) { self[i][j] = in.readDouble(); in.readByte(); //   ELEMENT } } }
      
      





一方で、余分な数バイトを書き込んでもアプリケーションのパフォーマンスやコードの可読性には影響しませんが、一方で、さまざまな形式をサポートする外部ストリームを実装するための追加の機会を提供します。



ストリームの観点から、この場合はMMInputStream / MMOutputStreamの場合、レコードは次のようになります。



以下は、MMOutputSteamクラスの実装の主要部分です。

 public class MMOutputStream extends OutputStream implements ObjectOutput { @Override public void writeByte(int v) throws IOException { switch (v) { case HEADER_MARKER: writeHeader(); break; case META_MARKER: writeMeta(); break; case ELEMENT_MARKER: writeElement(); break; } } @Override public void writeInt(int v) throws IOException { put(String.valueOf(v)); } @Override public void writeDouble(double v) throws IOException { put(String.format(Locale.US, "%.12f", v)); } @Override public void writeObject(Object obj) throws IOException { if (matrix instanceof SparseMatrix) { put(SPARSE_HEADER); } else if (matrix instanceof DenseMatrix) { put(DENSE_HEADER); } writeHeader(); matrix.writeExternal(this); flush(); } private void writeHeader() throws IOException { out.write("%%MatrixMarket "); out.write(buffer[0] + " "); out.write(buffer[1] + " "); out.write("real general"); out.newLine(); } private void writeMeta() throws IOException { dumpBuffer(); out.newLine(); } private void writeElement() throws IOException { dumpBuffer(); out.newLine(); } private void put(String value) { buffer[length++] = value; } private void dumpBuffer() throws IOException { for (int i = 0; i < length; i++) { out.write(buffer[i] + " "); } } }
      
      







まとめ



JavaでのI / Oパッケージの提案された実装は、Java APIの既存の階層へのBridgeテンプレートのかなり成功したアプリケーションです。 著者は、この記事で説明されているアイデアが読者の自由に使える別の便利なツールになり、このトピックに関する追加の議論につながることを望んでいます。



*



この記事で説明されている例は、線形代数問題la4jを解くためのオープンライブラリの一部です。 考えられているアイデアの実装は、 la4j.ioパッケージにあります。 現在のバージョンでは、MatrixMarket形式のみがサポートされています。



PSトピック作成者= author la4j。



All Articles