复习nio
简介:? JDK 1.4 中引入的新输入输出 (NIO) 库在标准 Java 代码中提供了高速的、面向块的 I/O。本实用教程从高级概念到底层的编程细节,非常详细地介绍了 NIO 库。您将学到诸如缓冲区和通道这样的关键 I/O 元素的知识,并考察更新后的库中的标准 I/O 是如何工作的。您还将了解只能通过 NIO 来完成的工作,如异步 I/O 和直接缓冲区。
import java.nio.FloatBuffer;public class UseFloatBuffer{ static public void main( String args[] ) throws Exception { FloatBuffer buffer = FloatBuffer.allocate( 10 );//分配新的 float 缓冲区。 新缓冲区的位置将为零,其界限将为其容量,其标记是未定义的。以 float 为单位 for (int i=0; i<buffer.capacity(); ++i) { float f = (float)(i+1); buffer.put( f ); } buffer.flip();//反转此缓冲区。首先对当前位置设置限制,然后将该位置设置为零。如果已定义了标记,则丢弃该标记。 在序列信道读取或 put 操作之后,调用此方法以做好序列信道写入或相对 get 操作。 while (buffer.hasRemaining()) {//判断在当前位置和限制之间是否有任何元素。 float f = buffer.get(); System.out.println( f ); } }}?
?
下一步是创建缓冲区:
?
下一步是创建一个缓冲区并在其中放入一些数据 - 在这里,数据将从一个名为?message?的数组中取出,这个数组包含字符串 "Some bytes" 的 ASCII 字节(本教程后面将会解释?buffer.flip()?和?buffer.put()?调用)。
?
回想一下 ,limit?决不能大于?capacity,此例中这两个值都被设置为 8。我们通过将它们指向数组的尾部之后(如果有第8个槽,则是第8个槽所在的位置)来说明这点。

position?设置为0。如果我们读一些数据到缓冲区中,那么下一个读取的数据就进入 slot 0 。如果我们从缓冲区写一些数据,从缓冲区读取的下一个字节就来自 slot 0 。?position?设置如下所示:

由于?capacity?不会改变,所以我们在下面的讨论中可以忽略它。
limit?没有改变。
limit?没有改变。
我们现在可以将数据从缓冲区写入通道了。?position?被设置为 0,这意味着我们得到的下一个字节是第一个字节。?limit?已被设置为原来的?position,这意味着它包括以前读到的所有字节,并且一个字节也不多。
缓冲区现在可以接收新的数据了。
import java.nio.ByteBuffer;public class TypesInByteBuffer{ static public void main( String args[] ) throws Exception { ByteBuffer buffer = ByteBuffer.allocate( 64 ); buffer.putInt( 30 ); buffer.putLong( 7000000000000L ); buffer.putDouble( Math.PI ); buffer.flip(); System.out.println( buffer.getInt() ); System.out.println( buffer.getLong() ); System.out.println( buffer.getDouble() ); }}?
?
import java.io.FileInputStream;import java.io.FileOutputStream;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;public class FastCopyFile{ static public void main( String args[] ) throws Exception { if (args.length<2) { System.err.println( "Usage: java FastCopyFile infile outfile" ); System.exit( 1 ); } String infile = args[0]; String outfile = args[1]; FileInputStream fin = new FileInputStream( infile ); FileOutputStream fout = new FileOutputStream( outfile ); FileChannel fcin = fin.getChannel(); FileChannel fcout = fout.getChannel(); ByteBuffer buffer = ByteBuffer.allocateDirect( 1024 );//直接字节缓冲区 while (true) { buffer.clear();//重设缓冲区,使它可以接受读入的数据。 int r = fcin.read( buffer ); if (r==-1) { break; } buffer.flip();//让缓冲区可以将新读入的数据写入另一个通道。 fcout.write( buffer ); } }}
还可以用内存映射文件创建直接缓冲区。
import java.net.InetSocketAddress;import java.nio.ByteBuffer;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;public class UseScatterGather{ static private final int firstHeaderLength = 2; static private final int secondHeaderLength = 4; static private final int bodyLength = 6; static public void main( String args[] ) throws Exception { if (args.length!=1) { System.err.println( "Usage: java UseScatterGather port" ); System.exit( 1 ); } int port = Integer.parseInt( args[0] ); ServerSocketChannel ssc = ServerSocketChannel.open(); InetSocketAddress address = new InetSocketAddress( port ); ssc.socket().bind( address ); int messageLength = firstHeaderLength + secondHeaderLength + bodyLength; ByteBuffer buffers[] = new ByteBuffer[3]; buffers[0] = ByteBuffer.allocate( firstHeaderLength ); buffers[1] = ByteBuffer.allocate( secondHeaderLength ); buffers[2] = ByteBuffer.allocate( bodyLength ); SocketChannel sc = ssc.accept(); while (true) { // Scatter-read into buffers分散读取 int bytesRead = 0; while (bytesRead < messageLength) { long r = sc.read( buffers ); bytesRead += r; System.out.println( "r "+r ); for (int i=0; i<buffers.length; ++i) { ByteBuffer bb = buffers[i]; System.out.println( "b "+i+" "+bb.position()+" "+bb.limit() ); } } // Process message here // Flip buffers for (int i=0; i<buffers.length; ++i) { ByteBuffer bb = buffers[i]; bb.flip(); } // Scatter-write back out//聚集写入 long bytesWritten = 0; while (bytesWritten<messageLength) { long r = sc.write( buffers ); bytesWritten += r; } // Clear buffers for (int i=0; i<buffers.length; ++i) { ByteBuffer bb = buffers[i]; bb.clear(); } System.out.println( bytesRead+" "+bytesWritten+" "+messageLength ); } }}?
?
在拥有锁之后,您可以执行需要的任何敏感操作,然后再释放锁:
import java.io.RandomAccessFile;import java.nio.channels.FileChannel;import java.nio.channels.FileLock;public class UseFileLocks{ static private final int start = 10; static private final int end = 20; static public void main( String args[] ) throws Exception { // Get file channel RandomAccessFile raf = new RandomAccessFile( "usefilelocks.txt", "rw" );//如果要获取一个排它锁,您必须以写方式打开文件。 FileChannel fc = raf.getChannel(); // Get lock System.out.println( "trying to get lock" ); FileLock lock = fc.lock( start, end, false );//排它锁 System.out.println( "got lock!" ); // Pause System.out.println( "pausing" ); try { Thread.sleep( 3000 ); } catch( InterruptedException ie ) {} // Release lock System.out.println( "going to release lock" ); lock.release();//释放锁 System.out.println( "released lock" ); raf.close(); }}??
import java.io.IOException;import java.net.InetSocketAddress;import java.net.ServerSocket;import java.nio.ByteBuffer;import java.nio.channels.SelectionKey;import java.nio.channels.Selector;import java.nio.channels.ServerSocketChannel;import java.nio.channels.SocketChannel;import java.util.Iterator;import java.util.Set;public class MultiPortEcho{ private int ports[]; private ByteBuffer echoBuffer = ByteBuffer.allocate( 1024 ); public MultiPortEcho( int ports[] ) throws IOException { this.ports = ports; go(); } private void go() throws IOException { // 第一件事就是创建一个 Selector//异步 I/O 中的核心对象名为 Selector。S//elector 就是您注册对各种 I/O 事件的兴趣的地方,而且当那些事件发生时,就是这个对象告诉您所发生的事件。 Selector selector = Selector.open(); // Open a listener on each port, and register each one // with the selector //我们将对不同的通道对象调用 register() 方法,以便注册我们对这些对象中发生的 I/O 事件的兴趣。register() 的第一个参数总是这个 Selector。 for (int i=0; i<ports.length; ++i) { //为了接收连接,我们需要一个 ServerSocketChannel。事实上,我们要监听的每一个端口都需要有一个 ServerSocketChannel 。对于每一个端口,我们打开一个 ServerSocketChannel ServerSocketChannel ssc = ServerSocketChannel.open();//创建一个新的 ServerSocketChannel ssc.configureBlocking( false );//将 ServerSocketChannel 设置为 非阻塞的 。我们必须对每一个要使用的套接字通道调用这个方法,否则异步 I/O 就不能工作。 ServerSocket ss = ssc.socket(); InetSocketAddress address = new InetSocketAddress( ports[i] ); ss.bind( address );//将ServerSocketChannel绑定到给定的端口 /** register() 的第一个参数总是这个 Selector。第二个参数是 OP_ACCEPT,这里它指定我们想要监听 accept 事件,也就是在新的连接建立时所发生的事件。这是适用于 ServerSocketChannel 的唯一事件类型。 register() 的调用的返回值SelectionKey 代表这个通道在此 Selector 上的这个注册。当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。 */ SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT );//将新打开的 ServerSocketChannels 注册到 Selector上 System.out.println( "Going to listen on "+ports[i] ); } //进入主循环 while (true) { int num = selector.select();//这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时, select() 方法将返回所发生的事件的数量。 Set selectedKeys = selector.selectedKeys(); Iterator it = selectedKeys.iterator(); while (it.hasNext()) { SelectionKey key = (SelectionKey)it.next(); /** 对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。 */ //监听新连接 if ((key.readyOps() & SelectionKey.OP_ACCEPT)//readyOps() 方法,并检查发生了什么类型的事件 == SelectionKey.OP_ACCEPT) { // Accept the new connection ServerSocketChannel ssc = (ServerSocketChannel)key.channel(); SocketChannel sc = ssc.accept();//因为我们知道这个服务器套接字上有一个传入连接在等待,所以可以安全地接受它;也就是说,不用担心 accept() 操作会阻塞 sc.configureBlocking( false );//将新连接的 SocketChannel 配置为非阻塞的。 // Add the new connection to the selector SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ );//而且由于接受这个连接的目的是为了读取来自套接字的数据,所以我们还必须将 SocketChannel 注册到 Selector上:注册用于 读取 而不是 接受 新连接。 //必须首先将处理过的 SelectionKey 从选定的键集合中删除。如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey it.remove(); System.out.println( "Got connection from "+sc ); } else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) { //在本例中,由于这是一个 echo server,我们只希望从套接字中读取数据并马上将它发送回去。 // Read the data SocketChannel sc = (SocketChannel)key.channel(); // Echo data int bytesEchoed = 0; while (true) { echoBuffer.clear(); int r = sc.read( echoBuffer ); if (r<=0) { break; } echoBuffer.flip(); sc.write( echoBuffer ); bytesEchoed += r; } System.out.println( "Echoed "+bytesEchoed+" from "+sc ); it.remove(); } }//System.out.println( "going to clear" );// selectedKeys.clear();//System.out.println( "cleared" ); // 返回主循环并接受从一个套接字中传入的数据(或者一个传入的 I/O 事件) } } static public void main( String args[] ) throws Exception { if (args.length<=0) { System.err.println( "Usage: java MultiPortEcho port [port port ...]" ); System.exit( 1 ); } int ports[] = new int[args.length]; for (int i=0; i<args.length; ++i) { ports[i] = Integer.parseInt( args[i] ); } new MultiPortEcho( ports ); }}?
?
?
然后,创建一个解码器(用于读取)和一个编码器 (用于写入):
import java.io.File;import java.io.RandomAccessFile;import java.nio.ByteBuffer;import java.nio.CharBuffer;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;import java.nio.charset.Charset;import java.nio.charset.CharsetDecoder;import java.nio.charset.CharsetEncoder;public class UseCharsets{ static public void main( String args[] ) throws Exception { String inputFile = "samplein.txt"; String outputFile = "sampleout.txt"; RandomAccessFile inf = new RandomAccessFile( inputFile, "r" ); RandomAccessFile outf = new RandomAccessFile( outputFile, "rw" ); long inputLength = new File( inputFile ).length(); FileChannel inc = inf.getChannel(); FileChannel outc = outf.getChannel(); MappedByteBuffer inputData =inc.map( FileChannel.MapMode.READ_ONLY, 0, inputLength ); Charset latin1 = Charset.forName( "ISO-8859-1" ); CharsetDecoder decoder = latin1.newDecoder(); CharsetEncoder encoder = latin1.newEncoder(); CharBuffer cb = decoder.decode( inputData ); // Process char data here ByteBuffer outputData = encoder.encode( cb ); outc.write( outputData ); inf.close(); outf.close(); }}?developerWorks,2002年7月)更好的了。?
- "" The ins and outs of Merlin's new I/O buffers "?(developerWorks,2003年3月)是介绍缓冲区基本知识的另一篇文章。?
- "" Character sets "?" (developerWorks,2002年10月)专门讨论字符集(特别是转换和编码模式)。?
- 通过 Kalagnanam 和 Balu G 的 "" Merlin brings nonblocking I/O to the Java platform "?(developerWorks,2002年3月)进一步了解 NIO。?
- Greg Travis 在他的 "?JDK 1.4 Tutorial”?(Manning 出版社,2002年3月)一书中仔细研究了 NIO。?
- 您可以在?developerWorks?Java 技术专区?找到数百篇关于 Java 编程的各个方面的文章。
?
?
?
感谢:http://www.ibm.com/developerworks/cn/education/java/j-nio/index.html