java new IO

来源:互联网 发布:生存分析数据库 编辑:程序博客网 时间:2024/06/07 04:30
原文地址:http://bbs.blueidea.com/thread-2679670-1-1.html,本文在此基础上做了一些修改。

PS:此文一般

Java New I/O的作用
当我们的JAVA程序会使用到大量的I/O操作,而程序性能很大一部分受到I/O影响的话,就可以考虑使用NIO来改写旧的I/O了~
所谓的NIO,就是JAVA类里的java.nio这个包~是J2SE1.4以后的版本才有的包~它提供了与传统I/O操作不同的解决方案,在I/O操作较多的时候,能在很大程度上提高程序性能。


基于旧的I/O的传统网络程序描述如下:
1)打开一个ServerSocket监听端口
2)程序在这里阻塞,直到有连接请求
3)读取请求、发送请求
4)关闭连接
5)重复2~4步


这就是传统的阻塞I/O操作,线程没有有效地利用cup,它们将大多数时间花费在I/O的阻塞上,所以这种线程是低效率的。


而非阻塞I/O以新的方式解决了阻塞问题,可以提高系统的效率。


NIO(new I/O)引入了四个概念:
·线性排列的数据缓冲,可读写缓冲
·一个字节码与Unicode字符的字符集映射
·双向通道Channels
·选择器Selectors




下面先来说说数据缓冲Buffer。
在一个Buffer中,position代表当前Buffer第一个能被读写的字节的位置,limit代表第一个不能被读写的字节的位置,capacity代表容量(这里读着有些拗口,是因为Buffer是双向的,如果读者一下子不能明白,可以看后面的解释)。对初始化的Buffer来说,position为0,limit=capacity。


在NIO中,通过Channel读写Buffer的数据。将一个Buffer中的数据写入Channel时必须调用Buffer对象的flip()方法(这里应该搞清楚,是Buffer与Channel交互的时候,才需要flip方法,如果是Buffer内部的自操作,是不需要的)。比如有以下代码:
ByteBuffer bb = ByteBuffer.allcateDirect(1024);
bb.put("this is a test".getBytes());
这两句的意思是:分配一个容量为1024的直接的ByteBuffer,通过put(byte[] b)方法将字符串存入到这个缓冲区bb里。而存在以下代码:
FileOutputStream out = new FileOutputStream("test.txt");
FileChannel channel = out.getChannel();
这时,当我们想把ByteBuffer对象里的数据写入到test.txt时,可以调用FileChannel的write(ByteBuffer)方法,但在这之前一定要先执行bb.flip(),然后再调用channel.write(bb);
flip()方法将缓冲区的position设为0,将limit设为现有数据的最后值。
如果不先调用flip(),则向test.txt里写入的东西就是空的~。


ByteBuffer是Buffer的子类,都位于java.nio包下面,该包下面还封装了其它基本数据类型的buffer,可以通过ByteBuffer的asCharBuffer()等方法得到其它基本类型的Buffer:


java.nio包如下:
ByteOrder                                 字节顺序的类型安全枚举。
Buffer                        一种用于特定的基本类型数据的容器。 
ByteBuffer                 字节缓冲区。  
CharBuffer                 字符缓冲区。 
DoubleBuffer                 double缓冲区。 
FloatBuffer                 float缓冲区。 
IntBuffer                 int缓冲区。 
LongBuffer                 long缓冲区。 
MappedByteBuffer         直接字节缓冲区,其内容是文件的内存映射区域。 
ShortBuffer                 short缓冲区。


二)字节码与Unicode字符的字符集映射


当需要将CharBuffer的存放的数据编码成ByteBuffer,再将ByteBuffer里存的数据解码成CharBuffer,这时候就要用到java.nio.charset.CharsetEncoder和java.nio.charset.CharsetDecoder了~~
(其实ByteBuffer有个asCharBuffer可以直接将ByteBuffer里的字节数据转换成字符数据的,但Charset类提供各种字符集比如utf-8、iso-8895-1等字符集的映射,可以将CharBuffer里的数据编码成各种格式的编码,然后再由CharsetDecoder解码)!下面是一个完整的程序代码,简单的展示了CharBuffer和ByteBuffer之间编码的转换:


package app.imo;


import java.nio.*;
import java.nio.charset.*;
public class CharsetTest {
        private ByteBuffer bb = null;
        private String s = "this is a test";
        public ByteBuffer encoder(){
                CharBuffer cb = CharBuffer.wrap(s);
                Charset cs = Charset.forName("UTF-8");
                CharsetEncoder ce = cs.newEncoder();
                try {
                        bb = ce.encode(cb); //将CharBuffer编码成ByteBuffer
                } catch (CharacterCodingException e) {
                        e.printStackTrace();
                }
                return bb;
        }
        public static void main(String[] args){
                CharsetTest ct = new CharsetTest();
                ByteBuffer bb = ct.encoder();
                Charset c = Charset.forName("UTF-8");
                CharsetDecoder cd = c.newDecoder();
                CharBuffer cb = null;
                try {
                        cb = cd.decode(bb);
                } catch (CharacterCodingException e) {
                        e.printStackTrace();
                }
                System.out.println(cb);
        }
}


三)NIO的核心技术:Selector、SelectableChannel和SelectionKey
说上面的三个类是NIO的核心技术我觉得一点都不为过~~
因为缓冲技术、字符集映射都是为它们准备的~~
三者位于java.nio.channels这个包里




Selector选择器,它将为我们监听所有的I/O操作,可以通过select()阻塞方法来确定什么时候在Channel中有数据流需要处理。可以由Selector类的静态方法Selector.open()获得一个Selector实例。Selector对象和SelectableChannel对象互为多路复用器。 
可以通过某个通道的register()方法注册到已经存在的选择器Selector。register()方法的声明如下:
public final SelectionKey register(Selector sel, int ops):向给定的选择器注册此通道,返回一个选择键。 
其中,sel - 要向其注册此通道的选择器,ops - 所得键的可用操作集。
ops可以有四个选择:
SelectionKey.OP_ACCEPT:               用于套接字接受操作的操作集位。
SelectionKey.OP_CONNECT:               用于套接字连接操作的操作集位。 
SelectionKey.OP_READ:                      用于读取操作的操作集位。
SelectionKey.OP_WRITE:                   用于写入操作的操作集位。


register()是SelectableChannel中定义的方法,SelectableChannel是一个抽象类,实现它的子类有DatagramChannel, Pipe.SinkChannel, Pipe.SourceChannel, ServerSocketChannel, SocketChannel。
要生成一个SelecableChannel实例,可以调用其子类的静态方法open(),比如说生成一个ServerSocketChannel实例,可以调用:
ServerSocketChannel ssc = ServerSocketChannel.open();
再通过调用ssc.socket().bind(InetSocketAddress)将ssc绑定到指定ServerSocket。
SelectableChannel的configureBlocking(boolean b)方法,设置为false时即将该通道配置为非阻塞的。
必须将通道配置成非阻塞的,才可以向Selector注册:
ssc.configureBlocking(false);
SelectionKey sk = ssc.register(selector,SelectionKey.OP_ACCEPT);
注册过以后,就可以调用selector.select()方法来选择一组相应的通道已为 I/O操作准备就绪的键了。


下面有二组代码,一组是基于NIO的,另一种是旧I/O的,大家可以对比参考下:
旧I/O客户端:
Socket s = new Socket("localhost",1111);
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
String str;
while((str=in.readLine())!=null){
        System.out.println(str);
}
旧I/O服务端:
ServerSocket ss = new ServerSocket(1111);
Socket c = ss.accept();//这里阻塞,标志着每一个ServerSocket都需要阻塞
PrintWriter out = new PrintWriter(c.getOutputStream(),true);
out.write("hello,client".getBytes());




NIO客户端:
SocketChannel channel = SocketChannel.open();
channel.connect(new InetSocketAddress("localhost",1111));
ByteBuffer buffer = ByteBuffer.allocateDirect(512);
channel.read(buffer);


NIO服务端:
Selector selector = Selector.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(1111));
ssc.configureBlocking(false);
ssc.register(selector,SelectionKey.OP_ACCEPT);
ssc.select();//这里阻塞,标志着所有ServerSocketChannel可以共用一个阻塞。
java.util.Set keys = ssc.selectedKeys();
java.util.Iterator i = keys.iterator();
while(i.hasNext()){
  SelectionKey key = i.next;
  i.remove();


  ServerSocketChannel serverSocket = (ServerSocketChannel)key.channel();
  SocketChannel socket = serverSocket.accept();
  ByteBuffer buf = ByteBuffer.wrap("hello,client\n".getBytes());
  socket.write(buf);
}




http://blog.sina.com.cn/s/blog_4c925dca0100hmvg.html
http://www.goldendoc.org/category/java-nio/

原创粉丝点击