Java NIO学習ノート(二)ChannelとBuffer


1.バッファ(Buffer)
Bufferは、書き込みまたは読み出すデータを含むオブジェクトです.NIOクラスライブラリにBufferオブジェクトを追加することは、新しいライブラリと元のI/Oの重要な違いを示しています.ストリーム向けI/Oでは、データを直接書き込み、またはストリームオブジェクトに直接読み込むことができます.Java NIOにおけるデータの読み書き操作は常にバッファに関連する.データはチャネルからバッファに読み込まれ、バッファからチャネルに書き込まれます.
バッファは実質的に配列である.通常はバイト配列(ByteBuffer)であり、他の種類の配列も使用できます.しかし、1つのバッファは配列だけでなく、データへの構造的なアクセスや読み書き位置の維持などの情報を提供します.
最も一般的なバッファはByteBufferであり、1つのByteBufferはbyte配列を操作するための機能のセットを提供します.ByteBufferのほかにもいくつかのバッファがありますが、実際には、Booleanタイプを除くJava基本タイプごとにバッファが対応しています.具体的には、次のようになります.
  • ByteBuffer:バイトバッファ
  • CharBuffer:文字バッファ
  • ShortBuffer:短整数バッファ
  • IntBuffer:整形バッファ
  • LongBuffer:ロング整形バッファ
  • FloatBuffer:浮動小数点型バッファ
  • DoubleBuffer:デュアル精度浮動小数点型バッファ
  • 各Bufferクラスは、Bufferインタフェースのサブインスタンスです.ByteBufferを除いて、各Bufferクラスにはまったく同じ操作がありますが、処理されるデータ型は異なります.ほとんどの標準I/O操作ではByteBufferが使用されているため、一般的なバッファの操作に加えて、ネットワークの読み書きを容易にするための独自の操作も提供されています.
    Bufferの基本的な使い方
    ByteBufferは最も一般的なバッファであり、他のデータ型を読み書きする方法を提供し、チャネルの読み書き方法はByteBufferのみを受信する.そのため、ByteBufferの使い方はしっかり身につける必要がある.
    1.ByteBuffer 1.1を作成allocate()スタティックメソッドByteBuffer buffer=ByteBufferを使用する.allocate(1024); 以上の方法では、1024バイトの容量のByteBufferを作成するが、作成されたバッファの容量が小さすぎることに気づいたら、適切なサイズのバッファを再作成するしかない.
    1.2 put()メソッドを用いる、ByteBufferを埋め込む、put()メソッドにより、ByteBufferに1バイトまたは複数の基本データ型の値を埋め込むことができる.
     public void readAllData(byte[] bytes){
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.put(bytes);
            }

    1.3既存の配列を包装することにより、包装方法により作成するバッファは、包装された配列内に保存するデータを保持する.
    ByteBuffer buffer=ByteBuffer.wrap(byteArray);
    

    文字列をByteBufferに格納場合は、ByetBuffer=ByteBufferとして次の操作が可能である.warp(“Hello World NIO”.getBytes()); 2.ループバックバッファとパージバッファ
      buffer.flip();
      buffer.clear();

    いったんread()を呼び出してFileChannelにByteBufferにバイトを格納ことを知らせると、バッファ上のflip()メソッドを呼び出す必要があり、このメソッドはバッファをデータの送出状態に準備するために使用され、以上のメソッドを実行すると、出力チャネルはデータの先頭から末尾ではなくデータの先頭から始まる.ロールバックはバッファ内のデータを保持し、読み取りではなく書き込みの準備をします.通俗的な理解は、読み取られる準備をさせることです(面倒そうに見えますが、最大の伝送速度のためです).バッファを使用してさらなるread()操作を実行する場合は、clear()を呼び出してread()ごとに準備する必要があります.
    前の記事では、指定したバイトサイズのファイルをByteBufferで読み込むだけでしたが、ByteBufferでテキストのすべての内容を読み取り、別のファイルに書き込むことができます.
     try {
                FileChannel inputFileChannel = new FileInputStream("/home/wang/bigchat.sql").getChannel();
                //newBigChat.sql     。         
                FileChannel  outputFileChannel = new FileOutputStream("/home/wang/newBigchat.sql").getChannel();
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                int len = 0;
                // read  -1          
                while ((len=inputFileChannel.read(byteBuffer))!=-1){
                    byteBuffer.flip();//    
                    outputFileChannel.write(byteBuffer);
                    byteBuffer.clear();//    
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
    

    flip()は、write()から情報を抽出できるようにバッファを準備する.write()の後もデータはバッファ内にあり、clear()メソッドは、バッファが別のread()操作中にデータを受け入れる準備ができるように、すべての内部ポインタを再配置する.
    実際には、上記のコードは理想的ではありません.transferTo()とtransferFrom()を使用すると、1つのチャネルと別のチャネルを直接接続できます.
     try {
                FileChannel inputFileChannel = new FileInputStream("/home/wang/bigchat.sql").getChannel();
                //newBigChat.sql     。         
                FileChannel outputFileChannel = new FileOutputStream("/home/wang/newBigchat1.sql").getChannel();
                inputFileChannel.transferTo(0,inputFileChannel.size(),outputFileChannel);
                outputFileChannel.transferFrom(inputFileChannel,0,inputFileChannel.size());
            }catch (IOException e){}
    

    この方法は理解すればよい.
    ByteBufferは通称バッファと呼ばれ、データを移動チャネルに移動する唯一の方法であり、独立した基本タイプのバッファしか作成できません.ByteBufferにはバイトが格納されており、それらを文字ストリームに変換するにはCharsetが必要であり、Charsetは文字符号化であり、バイトストリームを文字ストリーム(復号)に変換し、文字ストリームをバイトストリーム(符号化)に変換する方法を提供する.
    private byte[] getBytes (char[] chars) {//       (  )
       Charset cs = Charset.forName ("UTF-8");
       CharBuffer cb = CharBuffer.allocate (chars.length);
       cb.put (chars);
       cb.flip ();
       ByteBuffer bb = cs.encode (cb)
       return bb.array();
             }
    
    private char[] getChars (byte[] bytes) {//       (  )
          Charset cs = Charset.forName ("UTF-8");
          ByteBuffer bb = ByteBuffer.allocate (bytes.length);
          bb.put (bytes);
          bb.flip ();
          CharBuffer cb = cs.decode (bb);
          return cb.array();
    }

    ByteBufferでファイルを読み込み、CharBufferに変換して出力します.
     FileChannel fc = new FileInputStream(PATH).getChannel();
                    ByteBuffer buffer = ByteBuffer.allocate(SIZE);
                    fc.read(buffer);
                    //  ByteBuffer    ,                       
                    buffer.flip();
                    //  UTF-8   CharBuffer
                    Charset charset = Charset.forName("UTF-8");
                    CharBuffer cb = charset.decode(buffer);
                    System.out.println(cb);//      

    Bufferの4つのインデックス
    Bufferの構成にはデータのほかに4つのインデックスがあります.RandomAccessFileについてよく知っていれば、この4つのインデックスは分かりやすいです.
  • capacity:Bufferの最大データ容量、つまり最大何個のデータを格納できるかを示し、作成後は変更できません.
  • limit:読み取りまたは書き込みを行うべきでない最初のバッファ位置のインデックス.すなわちlimit後のデータは読み書きできない.
  • position:次の読み書き可能なバッファ位置を指すインデックス.
  • mark:タグを定義します.positionはmarkに直接位置決めできます.あまりよく使われていません
  • 上記の4つのインデックスの概念を持って、私たちが前に使ったflip()とclear()の方法を考えてみましょう.
    flipメソッドは、limitを現在のpositionの位置に設定し、positionを0に設定します.そしてBufferは出力データwrite()の準備をしました
    clearメソッドはBufferのクリアデータではなく、limitをcapactiy、positionを0に設定します.そしてBufferは入力データread()の準備をしました
    Bufferには、次のような一般的な方法があります.
  • boolean hasRemaining():positionとlimitの間に処理可能な要素があるかどうかを判断します.
  • int remaining():現在の位置と境界の間の要素数を返す
  • Buffer rewind():位置を0に設定し、設定したmarkをキャンセルします.

  • Bufferはpostion,limit,markを操作する方法を提供する以外に.もう2つの重要な方法があります.put()とget()は、Bufferにデータを入れ、Bufferからデータを取り出すために使用されます.Bufferは、単一のデータへのアクセスをサポートし、バッチ・データへのアクセスもサポートします(配列はパラメータ).
    2.チャンネル(Channel)
    Java NIOのチャネルはバッファよりもよく理解されています.ストリームに似ていますが、いくつかの違いがあります.
  • は、チャネルからデータを読み出すこともできるし、チャネルにデータを書き込むこともできる.しかし、ストリームの読み書きは通常一方向である.
  • チャネルは、非同期で読み書きすることができる.
  • チャネルのデータは、常に1つのBufferに読み出されるか、または常に1つのBufferから書き込まれる.

  • 前述したように、チャネルからバッファにデータを読み出し、バッファからチャネルにデータを書き込む.チャネルはフルデュプレクスであるため、ストリームよりも下位オペレーティングシステムのAPIをよりよくマッピングすることができる.特にUNIXネットワークプログラミングモデルでは,下位オペレーティングシステムのチャネルは全二重であり,読み書き動作をサポートする.
    前述した3つの基礎ストリーム呼び出しgetChannel()メソッドにより1つのChannelの実装FileChannelを取得した.次に、NIOの他のチャネルの実装を見てみましょう.
  • FileChannel:ファイルからデータを読み書きします.
  • DatagramChannel:UDPでネットワーク内のデータを読み書きできます.
  • SocketChannel:TCPを通じてネットの中のデータを読み書きすることができます.
  • ServerSocketChannel:Webサーバのように、新しく入ってきたTCP接続を傍受できます.新しい接続ごとにSocketChannelが作成されます.

  • Channelには、ネットワークの読み書きに使用されるSelectableChannelとファイル操作に使用されるFileChannelの2つのクラスしかありません.前述のDatagramChannel,SocketChannel,ServerSocketChannelはいずれもSelectableChannelのサブクラスである