チャネルは、java.nioの2番目の主要なイノベーションです。これらは拡張でも拡張でもありませんが、I / Oサービスへの直接接続を提供する新しく優れたJavaI / Oの例です。チャネルは、バイトバッファとチャネルの反対側のエンティティ(通常はファイルまたはソケット)の間でデータを効率的に転送するために使用されます。
チャンネル紹介
チャネルは、I / Oサービスにアクセスするためのコンジットです。I / Oは、ファイルI / OとストリームI / Oの2つの大きなカテゴリに分類できます。したがって、それに対応して2つのタイプのチャネルがあることは驚くべきことではありません。それらはファイルチャネルとソケットチャネルです。APIにはFileChannelクラスと3つのソケットチャネルクラス(SocketChannel、ServerSocketChannel、DatagramChannel)があることがわかります。
チャネルはさまざまな方法で作成できます。ソケットチャネルには、新しいソケットチャネルを直接作成できるファクトリメソッドがあります。ただし、FileChannelオブジェクトは、開いているRandomAccessFile、FileInputStream、またはFileOutputStreamオブジェクトでgetChannel()メソッドを呼び出すことによってのみ取得できます。FileChannelオブジェクトを直接作成することはできません。
まず、FileChannelの使用法を見てみましょう。
// 创建文件输出字节流
FileOutputStream fos = new FileOutputStream("data.txt");
//得到文件通道
FileChannel fc = fos.getChannel();
//往通道写入ByteBuffer
fc.write(ByteBuffer.wrap("Some text ".getBytes()));
//关闭流
fos.close();
//随机访问文件
RandomAccessFile raf = new RandomAccessFile("data.txt", "rw");
//得到文件通道
fc = raf.getChannel();
//设置通道的文件位置 为末尾
fc.position(fc.size());
//往通道写入ByteBuffer
fc.write(ByteBuffer.wrap("Some more".getBytes()));
//关闭
raf.close();
//创建文件输入流
FileInputStream fs = new FileInputStream("data.txt");
//得到文件通道
fc = fs.getChannel();
//分配ByteBuffer空间大小
ByteBuffer buff = ByteBuffer.allocate(BSIZE);
//从通道中读取ByteBuffer
fc.read(buff);
//调用此方法为一系列通道写入或相对获取 操作做好准备
buff.flip();
//从ByteBuffer从依次读取字节并打印
while (buff.hasRemaining()){
System.out.print((char) buff.get());
}
fs.close();
SocketChannelをもう一度見てみましょう。
SocketChannel sc = SocketChannel.open( );
sc.connect (new InetSocketAddress ("somehost", someport));
ServerSocketChannel ssc = ServerSocketChannel.open( );
ssc.socket( ).bind (new InetSocketAddress (somelocalport));
DatagramChannel dc = DatagramChannel.open( );
SocketChannelをノンブロッキングモードに設定できます。設定後、非同期モードでconnect()、read()、write()を呼び出すことができます。SocketChannelが非ブロッキングモードであり、この時点でconnect()を呼び出すと、接続が確立される前にメソッドが戻る場合があります。接続が確立されているかどうかを判別するために、finishConnect()メソッドを呼び出すことができます。このような:
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
while(! socketChannel.finishConnect() ){
//wait, or do something else...
}
サーバー側での使用では、多くのソケットチャネルを同時に管理しやすくなるため、非ブロッキングソケットチャネルが考慮されることがよくあります。ただし、クライアント側で1つまたは複数の非ブロッキングソケットチャネルを使用することも有益です。たとえば、非ブロッキングソケットチャネルを使用すると、GUIプログラムはユーザーの要求に焦点を合わせ、同時に1つ以上のサーバーとのセッションを維持できます。 。多くのプログラムでは、ノンブロッキングモードが便利です。
finishConnect()メソッドを呼び出して、接続プロセスを完了します。このメソッドは、いつでも安全に呼び出すことができます。非ブロッキングモードのSocketChannelオブジェクトでfinishConnect()メソッドが呼び出されると、次のいずれかの状況が発生する可能性があります。
- connect()メソッドはまだ呼び出されていません。NoConnectionPendingExceptionが生成されます。
- 接続確立プロセスは進行中であり、まだ完了していません。その後、何も起こらず、finishConnect()メソッドはすぐにfalseを返します。
- 非ブロッキングモードでconnect()メソッドを呼び出した後、SocketChannelはブロッキングモードに戻ります。次に、必要に応じて、接続が確立されるまで呼び出し元のスレッドがブロックされ、finishConnect()メソッドが真の値を返します。connect()への最初の呼び出しまたはfinishConnect()への最後の呼び出しの後、接続確立プロセスは完了しています。次に、SocketChannelオブジェクトの内部状態が接続状態に更新され、finishConnect()メソッドがtrue値を返し、SocketChannelオブジェクトを使用してデータを送信できます。
- 接続が確立されました。その後、何も起こらず、finishConnect()メソッドは真の値を返します。
ソケットチャネルはスレッドセーフです。同時アクセス中は、アクセスを開始する複数のスレッドを保護するための特別な対策は必要ありませんが、常に1つの読み取り操作と1つの書き込み操作のみが進行中です。ソケットはパケット指向ではなくストリーム指向であることを忘れないでください。送信されたバイトが順番に到着することを保証できますが、バイトのグループ化を維持することを約束することはできません。特定の送信者はソケットに20バイトを書き込むことができ、受信者はread()メソッドを呼び出すときに3バイトしか受信しません。残りの17バイトはまだ送信中です。このため、複数の非協調スレッドがストリームソケットの同じ側を共有できるようにすることは、決して良い設計上の選択ではありません。
最後に、DatagramChannelを見てください。
最後のソケットチャネルはDatagramChannelです。SocketChannelがSocketに対応し、ServerSocketChannelがServerSocketに対応するように、各DatagramChannelオブジェクトにもDatagramSocketオブジェクトが関連付けられています。ただし、元の命名パターンはここでは適用されません。「DatagramSocketChannel」は少し不器用に見えるため、簡潔な名前「DatagramChannel」が採用されています。
SocketChannelがコネクション型ストリーミングプロトコル(TCP / IPなど)をシミュレートするのと同様に、DatagramChannelはパケット指向のコネクションレス型プロトコル(UDP / IPなど)をシミュレートします。
DatagramChannelの作成パターンは、他のソケットチャネルの作成と同じです。静的open()メソッドを呼び出して、新しいインスタンスを作成します。新しいDatagramChannelには、socket()メソッドを呼び出すことで取得できるピアDatagramSocketオブジェクトが含まれます。DatagramChannelオブジェクトは、サーバー(リスナー)またはクライアント(送信者)のいずれかとして機能できます。新しく作成されたチャネルに監視を任せる場合は、最初にチャネルをポートまたはアドレス/ポートの組み合わせにバインドする必要があります。DatagramChannelのバインドは、通常のDatagramSocketのバインドと同じです。どちらも、ピアソケットオブジェクトにAPIを委託することで実装されます。
DatagramChannel channel = DatagramChannel.open( );
DatagramSocket socket = channel.socket( );
socket.bind (new InetSocketAddress (portNumber));
DatagramChannelはコネクションレス型です。各データグラムは、他のデータグラムに依存しない独自の宛先アドレスとデータペイロードを持つ自己完結型のエンティティです。ストリーム指向のソケットとは異なり、DatagramChannelは別々のデータグラムを異なる宛先アドレスに送信できます。同様に、DatagramChannelオブジェクトも任意のアドレスからデータパケットを受信できます。到着する各データグラムには、それがどこから来たのか(送信元アドレス)に関する情報が含まれています。
バインドされていないDatagramChannelは、引き続きデータパケットを受信できます。基になるソケットが作成されると、動的に生成されたポート番号がそれに割り当てられます。バインディング動作では、チャネルに関連付けられたポートが特定の値に設定されている必要があります(このプロセスにはセキュリティチェックまたはその他の検証が含まれる場合があります)。チャネルがバインドされているかどうかに関係なく、送信されるすべてのパケットには、DatagramChannelの送信元アドレス(ポート番号付き)が含まれます。Unbound DatagramChannelは、そのポートに送信されたパケット、通常はチャネルの前に前後に送信されたパケットを受信できます。バインドされたチャネルは、バインド先の既知のポートに送信されたパケットを受信します。データの実際の送信または受信は、send()メソッドとreceive()メソッドを介して行われます。
注:指定したByteBufferに、受信しているデータパケットを格納するのに十分な空き領域がない場合、埋められていないバイトはサイレントに破棄されます。
スキャッター/ギャザー
チャネルは、Scatter / Gather(ベクトルI / Oと呼ばれることもあります)と呼ばれる重要な新機能を提供します。これは、複数のバッファでの単純なI / O操作の実現を指します。書き込み操作の場合、データは複数のバッファーから順次抽出され(gatherと呼ばれます)、チャネルに沿って送信されます。バッファ自体が収集する機能を持っている必要はありません(通常、バッファにはこの機能がありません)。収集プロセスの効果は、データを送信する前に、すべてのバッファーの内容が連結され、大きなバッファーに格納されるようなものです。読み取り操作の場合、チャネルから読み取られたデータは複数のバッファーに順次分散(スキャッターと呼ばれます)され、チャネル内のデータまたはバッファーの最大スペースが消費されるまで各バッファーがいっぱいになります。
スキャッター/ギャザーは、送信データを個別に処理する必要がある場合によく使用されます。たとえば、メッセージヘッダーとメッセージ本文で構成されるメッセージを送信する場合、メッセージ本文とメッセージヘッダーを異なるバッファーに分散して、次のようにすることができます。メッセージヘッダーとメッセージ本文を簡単に処理できます。
散乱読み取りとは、データが1つのチャネルから複数のバッファーに読み取られることを意味します。次の図で説明します。
コード例は次のとおりです。
ByteBuffer header = ByteBuffer.allocateDirect (10);
ByteBuffer body = ByteBuffer.allocateDirect (80);
ByteBuffer [] buffers = { header, body };
int bytesRead = channel.read (buffers);
書き込みの収集とは、複数のバッファーから同じチャネルにデータを書き込むことです。次の図で説明します。
コード例は次のとおりです。
ByteBuffer header = ByteBuffer.allocateDirect (10);
ByteBuffer body = ByteBuffer.allocateDirect (80);
ByteBuffer [] buffers = { header, body };
channel.write(bufferArray);
適切に使用すると、Scatter / Gatherは非常に強力なツールになります。これにより、オペレーティングシステムにハードワークの完了を任せることができます。つまり、読み取ったデータを複数のバケットに分割したり、異なるデータブロックを全体にマージしたりできます。この種の作業を実行するためにオペレーティングシステムが高度に最適化されているため、これは大きな成果です。データを前後に移動する手間が省け、バッファのコピーが回避され、書き込みとデバッグに必要なコードの量が削減されます。基本的にデータコンテナ参照を提供してデータを組み合わせるため、さまざまな組み合わせに従って複数のバッファ配列参照を作成すると、さまざまなデータブロックをさまざまな方法で組み合わせることができます。次の例は、この点をよく示しています。
public class GatheringTest {
private static final String DEMOGRAPHIC = "output.txt";
public static void main (String [] argv) throws Exception {
int reps = 10;
if (argv.length > 0) {
reps = Integer.parseInt(argv[0]);
}
FileOutputStream fos = new FileOutputStream(DEMOGRAPHIC);
GatheringByteChannel gatherChannel = fos.getChannel();
ByteBuffer[] bs = utterBS(reps);
while (gatherChannel.write(bs) > 0) {
// 不做操作,让通道把数据输出到文件写完
}
System.out.println("Mindshare paradigms synergized to " + DEMOGRAPHIC);
fos.close();
}
private static String [] col1 = { "Aggregate", "Enable", "Leverage",
"Facilitate", "Synergize", "Repurpose",
"Strategize", "Reinvent", "Harness"
};
private static String [] col2 = { "cross-platform", "best-of-breed", "frictionless",
"ubiquitous", "extensible", "compelling",
"mission-critical", "collaborative", "integrated"
};
private static String [] col3 = { "methodologies", "infomediaries", "platforms", "schemas", "mindshare", "paradigms", "functionalities", "web services", "infrastructures" };
private static String newline = System.getProperty ("line.separator");
private static ByteBuffer [] utterBS (int howMany) throws Exception {
List list = new LinkedList();
for (int i = 0; i < howMany; i++) {
list.add(pickRandom(col1, " "));
list.add(pickRandom(col2, " "));
list.add(pickRandom(col3, newline));
}
ByteBuffer[] bufs = new ByteBuffer[list.size()];
list.toArray(bufs);
return (bufs);
}
private static Random rand = new Random( );
/**
* 随机生成字符
* @param strings
* @param suffix
* @return
* @throws Exception
*/
private static ByteBuffer pickRandom (String [] strings, String suffix) throws Exception {
String string = strings [rand.nextInt (strings.length)];
int total = string.length() + suffix.length( );
ByteBuffer buf = ByteBuffer.allocate (total);
buf.put (string.getBytes ("US-ASCII"));
buf.put (suffix.getBytes ("US-ASCII"));
buf.flip( );
return (buf);
}
}
出力は次のとおりです。
統合されたWebサービスを再発明する
最善のプラットフォームを集約する
摩擦のないプラットフォームを
活用する拡張可能なパラダイムを再利用する統合された方法
論を
再利用する
ミッションクリティカルなパラダイムを
促進する説得力のある方法論を促進する
説得力のある機能を再発明
する拡張可能なプラットフォームを促進する
この出力は無意味ですが、gatherは非常に簡単に出力できます。
パイプ
java.nio.channelsパッケージには、Pipeというクラスが含まれています。大まかに言えば、パイプは2つのエンティティ間で一方向にデータを送信するために使用される導管です。
Java NIOパイプラインは、2つのスレッド間の一方向のデータ接続です。パイプには、ソースチャネルとシンクチャネルがあります。データはシンクチャネルに書き込まれ、ソースチャネルから読み取られます。Pipeクラスは、ループバックメカニズムを提供するChannelオブジェクトのペアを作成します。これら2つのチャネルのリモートエンドは、SinkChannelオブジェクトに書き込まれたデータがSourceChannelオブジェクトに表示されるように接続されています。
パイプを作成して、パイプにデータを書き込みましょう。
//通过Pipe.open()方法打开管道
Pipe pipe = Pipe.open();
//要向管道写数据,需要访问sink通道
Pipe.SinkChannel sinkChannel = pipe.sink();
//通过调用SinkChannel的write()方法,将数据写入SinkChannel
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
sinkChannel.write(buf);
}
パイプラインからデータを読み取る方法を見てください。
パイプラインのデータを読み取るには、ソースチャネルにアクセスする必要があります。
Pipe.SourceChannel sourceChannel = pipe.source();
ソースチャネルのread()メソッドを呼び出して、データを読み取ります。
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = sourceChannel.read(buf);
ead()メソッドによって返されるint値は、バッファに読み込まれたバイト数を示します。
この時点で、チャネルの簡単な使用法は終了しました。使用する場合は、より多くの練習をしてシミュレーションで使用し、いつどのように使用するかを知る必要があります。次のセクションでは、セレクターについて話す-セレクター。