Java新IO

  Java从1.4开始引进了对于输入输出的改进,相关类位于java.nio包中。新IO主要有以下几个特性:

(1)字符集编码器和解码器

(2)非阻塞的IO

(3)内存映射文件

 

1. 字符集编码器和解码器

  Charset类表示不同的字符集,可以使用Charset.forName方法获得指定名称的字符集对象,与Charset相关的类在java.nio.charset包中。

(1)编码

   将Unicode编码的字符串编码成指定编码的字节序列

   Charset cset = Charset.forName("UTF-8");

   String str = "Charset类";

   ByteBuffer buffer = cset.encode(str);

   byte[]  bytes = buffer.array();

(2)解码

   将字节序列解码成Unicode编码的字符串

   Charset cset = Charset.forName("UTF-8");

   ByteBuffer buffer = ByteBuffer.wrap(bytes,0,bytes.length);

   String str = cset.decode(buffer).toString();

 

2. Buffer类

  用于保存基本数据类型数据的缓冲区,其常用的子类有ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer。

   缓冲区有三个基本属性:

位置,下一个要读写的元素在缓冲区中的位置;

界限,缓冲区中不允许读写的第一个元素的位置;

容量,缓冲区能包含的元素的数量,缓冲区的容量是固定的,在创建缓冲区的时候指定。

  (1)读缓冲区

  读缓冲区,即从缓冲区中读取数据,通常对应着其他类的write方法,即将缓冲区中的内容读出然后写到某个地方。例:

ByteBuffer  buffer = ByteBuffer.allocate(256);

buffer.flip();  //将界限设置到当前位置,将位置复位到0,使缓冲区变成可读状态

FileChannel  fileChannel = new FileOutputStream("filename");

fileChannel.write(buffer); //读缓冲区的内容(从位置到界限)写到文件通道

  从位置到界限之间的内容即缓冲区中的剩余内容,这段内容是可读或者可写的。

(2)写缓冲区

  写缓冲区,即向缓冲区中写入数据,通常对应着其他类的read方法,即将读到内容写到缓冲区中保存。例:

ByteBuffer  buffer = ByteBuffer.allocate(256);

buffer.clear();  //将位置复位到0,将界限设置成容量,使缓冲区变成可写状态

FileChannel  fileChannel = new FileInputStream("filename");

fileChannel.read(buffer); //从文件通道读取内容写到缓冲区中

 

3. 非阻塞IO

   在java.nio.channels包中包含Channel相关的类,Channel表示一个到硬件设备、文件、网络套接字等的一个I/O通道,Channel是可读也可写的双向通道,这是Channel与java.io包中的流式I/O的一个很大的区别,另外Channel的读写操作可以设置为阻塞/非阻塞的。

   Channel类常见的子类有:FileChannel、DatagramChannel、SocketChannel、ServerSocketChannel等,分别表示到文件、数据报、TCP套接字的通道。

   java.nio.channels包中还包含Selector类和SelectionKey类,Selector类表示多路复用器,SelectionKey是多路复用器中用来标记注册上来的通道操作属性。通过Selector和SelectionKey可以用来实现多路复用的I/O,例如:可以实现select模式的网路I/O。

   下面给出简单实现的java实现select模式的IO:

//服务器端
public class ServerTask implements Runnable
{
    private Selector selector;
    private ServerSocketChannel serverChannel;
    private Iterator<SelectionKey> keyItera;
    private ByteBuffer buffer;
    
    public ServerTask() throws IOException
    {
        serverChannel = ServerSocketChannel.open();      // 创建服务器端套接字通道
        serverChannel.socket().bind(new InetSocketAddress(9898));  // 绑定到本机的9898端口
        selector = Selector.open();                      // 创建Selector多路I/O复用器
//设置通道非阻塞并注册该通道到ACCEPT事件监听集合中 serverChannel.configureBlocking(
false).register(selector,SelectionKey.OP_ACCEPT); buffer = ByteBuffer.allocate(128); } @Override public void run() { SelectionKey key = null; while(true) { try { if(selector.select() > 0) // 选择一组键,其对应的通道已经为某个I/O操作准备就绪,返回选择的键的数目 { keyItera = selector.selectedKeys().iterator(); while(keyItera.hasNext()) { key = keyItera.next(); if(key.isAcceptable()) // ACCPET操作准备就绪 {
// 接受客户端的连接 SocketChannel clientChannel
= ((ServerSocketChannel)key.channel()).accept();
// 设置客户端套接字通道非阻塞,并注册到READ事件监听集合中
if(clientChannel != null) clientChannel.configureBlocking(false).register(selector,SelectionKey.OP_READ); } else if(key.isReadable()) // READ操作准备就绪 {
// 从客户端套接字通道读取数据保存到缓冲区中 SocketChannel clientChannel
= ((SocketChannel)key.channel()); buffer.clear(); clientChannel.read(buffer); buffer.flip(); System.out.print(buffer.getChar()); System.out.println(buffer.getInt()); } keyItera.remove(); // 移除该键,表示已经处理 } } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
//客户端
public
class ClientTask implements Runnable { private SocketChannel channel; private char name; private ByteBuffer buffer; private int count = 0; public ClientTask(char name) throws IOException { this.name = name;
//创建客户端套接字通道,连接到服务器端
channel
= SocketChannel.open(new InetSocketAddress("127.0.0.1",9898)); buffer = ByteBuffer.allocate(128); } @Override public void run() { while(true) { count++; try {
//将缓冲区中的内容写入到客户端套接字通道,发送给服务器端 buffer.putChar(name); buffer.putInt(count); buffer.flip(); channel.write(buffer); buffer.clear(); Thread.sleep(
1000); // 休眠1s } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
//主程序
public
class Test { public static void main(String[] args) { try { new Thread(new ServerTask()).start(); //启动服务器端线程 for(int i = 0; i < 3; i++) //启动多个客户端线程 { new Thread(new ClientTask((char)('A' + i))).start(); } } catch(Exception e) { e.printStackTrace(); } }

 服务器端接收到来自多个客户端的信息并打印出来,输出结果:

A1
B1
C1
A2
B2
C2

 

4. 内存映射文件

  操作系统可以利用虚拟内存机制实现将一个文件或者文件的部分映射到内存中,然后对这个文件的操作就像访问内存一样,比传统的文件流式I/O要快得多。

  首先,创建一个FileChannel;

  然后调用FileChannel类得map方法从这个通道中获得一个MappedByteBuffer,即将此文件映射到虚拟内存中。可以指定映射文件的部分以及映射模式,包括以下三种模式:

  FileChannel.MapMode.READ_ONLY,只读模式;

  FileChannel.MapMode.READ_WRITE,可读写模式,对缓冲区的修改会写回到文件中;

  FileChannel.MapMode.PRIVATE,可读写模式,但是对缓冲区的修改不会写回到文件中;

 然后就可以通过获得的MappedByteBuffer对象对文件进行读写操作了。

   

   

参考资料 《Java核心技术》

posted @ 2015-08-16 22:00  jqc  阅读(296)  评论(0编辑  收藏  举报