java 新 IO(6)

来源:互联网 发布:e宠商城淘宝店 编辑:程序博客网 时间:2024/05/15 21:59


问题:如果一个程序现在需要等待用户输入数据,则可以通过 system.in 来完成。但是这样一来,在使用时就会出现一个问题:
如果用户没有输入信息,则肯定会一直等待用户输入,大量的系统资源就会被败白白浪费。所以在 jdk 1.4 之后引入了 新IO 处理机制。---NIO .

IO的阻塞操作:
我们知道 在IO操作中从键盘解说数据是我们会使用readLine()方法,程序就要停止等待用户输入数据;在网络编程中我们知道在服务器端使用ServletSocket 类的accept() 方法时,服务器会一直处于等待操作,等待客户端连接。这两类操作都属于阻塞操作,因为都会让程序暂停。

新IO并没有在原来的IO基础上开发,而是采用了全新的类和接口,除了原有的功能之外还提供了一下新的特性:
    多路选择的非封锁式I/O设施;
    支持文件锁和内存映射;
    支持正则表达式的模式匹配设施;
    字符集编码器和译码器。
    在新IO中使用 Buffer 和Channel 支持以上的操作。
    
一、缓冲区与Buffer
1、基本操作:
在基本IO操作中所有的操作都是直接一流的形式完成的;而在NIO中所有的操作都要使用缓冲区处理,且所有的读写操作都是通过缓冲区完成的。缓冲区(Buffer)是一个线性的、有序的数据集,只能容纳某种特定的事数据类型。

java.nio.Buffer 本身是一个抽象类;
Buffer有四个基本属性:
    1、capacity  容量,buffer能够容纳的最大元素数目,在Buffer创建时设定并不能更改
    2、limit buffer中有效位置数目
    3、position 下一个读或者写的位置
    4、mark  用于记忆的标志位,配合reset()使用,初始值未设定,调用mark后将当前position设为值

    四者关系:0 <= mark <= position <= limit <= capacity
API:
    package java.nio;
    public abstract class Buffer {
    public final int capacity( )
    public final int position( )
    public final Buffer position (int newPosition)
    public final int limit( )
    public final Buffer limit (int newLimit)
    public final Buffer mark( )
    public final Buffer reset( )
    public final Buffer clear( )
    public final Buffer flip( )
    public final Buffer rewind( )
    public final int remaining( )
    public final boolean hasRemaining( )
    public abstract boolean isReadOnly( );
    }

    支持链式调用,如:buffer.mark().position(5).reset( );
    注意isReadOnly()方法决定了buffer是否可写。

下面以IntBuffer类的操作为实例:
代码:

package com.zsc.day02;import java.nio.IntBuffer;public class IntBufferDemo {    public static void main(String[] args) {           //声明int类型缓冲区   ,开辟缓冲区大小为10        IntBuffer buf=IntBuffer.allocate(10);        System.out.println("1.写入数据之前:position="+buf.position()+"," +                  "limit="+buf.limit()+",capacity="+buf.capacity());          int temp[]={2,3,4,8,3,4};          buf.put(1);   //写入数据          buf.put(temp);          System.out.println("2.写入数据之后:position="+buf.position()+"," +                  "limit="+buf.limit()+",capacity="+buf.capacity());          buf.flip(); // 重置缓冲区 即position=0,limit=原本position           System.out.println("3.准备输出数据时:position="+buf.position()+"," +                  "limit="+buf.limit()+",capacity="+buf.capacity());          //输出缓冲去内容          while(buf.hasRemaining()){              int x=buf.get();              System.out.print(x+" ");          }      }  }



运行结果
1.写入数据之前:position=0,limit=10,capacity=102.写入数据之后:position=7,limit=10,capacity=103.准备输出数据时:position=0,limit=7,capacity=101 2 3 4 8 3 4 



如果只开辟了5个缓冲区大小,但是写入了7个数据时,则会发生以下错误:
Exception in thread "main" java.nio.BufferOverflowException
代码
package com.zsc.day02;import java.nio.IntBuffer;public class IntBufferDemo {    public static void main(String[] args) {           //声明int类型缓冲区   ,开辟缓冲区大小为5        IntBuffer buf=IntBuffer.allocate(5);        System.out.println("1.写入数据之前:position="+buf.position()+"," +                  "limit="+buf.limit()+",capacity="+buf.capacity());          int temp[]={2,3,4,8,3,4};          buf.put(1);   //写入数据          buf.put(temp);          System.out.println("2.写入数据之后:position="+buf.position()+"," +                  "limit="+buf.limit()+",capacity="+buf.capacity());          buf.flip(); // 重置缓冲区 即position=0,limit=原本position           System.out.println("3.准备输出数据时:position="+buf.position()+"," +                  "limit="+buf.limit()+",capacity="+buf.capacity());          //输出缓冲去内容          while(buf.hasRemaining()){              int x=buf.get();              System.out.print(x+" ");          }      }  }
运行结果
Exception in thread "main" java.nio.BufferOverflowException    at java.nio.HeapIntBuffer.put(HeapIntBuffer.java:183)    at java.nio.IntBuffer.put(IntBuffer.java:832)    at com.zsc.day02.IntBufferDemo.main(IntBufferDemo.java:11)

2、创建子缓冲区:
可以使用各个缓冲区类的slice()方法从一个缓冲区中创建一个新的子缓冲区,子缓冲区与原缓冲区中的部分数据可以共享。
代码:
package com.zsc.day02;import java.nio.IntBuffer;import java.nio.IntBuffer;  public class IntBufferDemo02 {      public static void main(String[] args) {          IntBuffer buf=IntBuffer.allocate(10); //声明int类型缓冲区  开辟10个大小缓冲区        IntBuffer sub=null;  //定义缓冲区对象        for(int i=0;i<10;i++){              buf.put(2*i+1);  //加入10个奇数        }          //创建子缓冲区          buf.position(2);  //主缓冲区设置在第三个元素上        buf.limit(6);      //主缓冲区limit 为6        sub=buf.slice();      //开辟子缓冲区        for(int i=0;i<sub.capacity();i++){              int temp=sub.get(i);      //根据下表去的元素            sub.put(temp-1);  //改变子缓冲区内容          }          buf.flip();//重设缓冲区          buf.limit(buf.capacity());     //设置limit        //输出缓冲区内容          while(buf.hasRemaining()){  //只要缓冲区中有数据则输出            int x=buf.get();  // 取出当前内容            System.out.print(x+" ");          }      }  }  

运行结果:
1 3 4 6 8 10 13 15 17 19 

3、创建只读缓冲区
如果现在要使用到缓冲区中的内容,但又不希望其内容被修改,则可以通过 asReadOnlyBuffer()方法创建一个只读缓冲区,但是创建完毕以后,此缓冲区不能变为可写状态。

代码:
package com.zsc.day02;import java.nio.IntBuffer;import java.nio.IntBuffer;  public class IntBufferDemo03 {      public static void main(String[] args) {          IntBuffer buf=IntBuffer.allocate(10); //声明int类型缓冲区  开辟10个大小缓冲区        IntBuffer readOnly=null;          for(int i=0;i<10;i++){              buf.put(2*i+1);          }          //创建子缓冲区          buf.position(2);          buf.limit(6);          readOnly=buf.asReadOnlyBuffer();          readOnly.flip();//重设缓冲区          readOnly.limit(buf.capacity());          //输出缓冲区内容          while(readOnly.hasRemaining()){              int x=readOnly.get();              System.out.print(x+" ");          }          System.out.println();//        readOnly.put(30);   //错误不可写    }  }  

运行结果:
1 3 5 7 9 11 13 15 17 19 

4、直接创建缓冲区:
只缓冲区操作中,只有ByteBuffer 可以创建直接缓冲区,这样java虚拟机将尽最大努力直接对其执行本机的IO操作。
创建方法:public static ByteBuffer allocateDirect(int capacity)
代码:
package com.zsc.day02;import java.nio.ByteBuffer;public class ByteBufferDemo {      public static void main(String[] args) {          //声明ByteBuffer 对象        ByteBuffer buf = null ;        //直接开辟缓冲区        buf = ByteBuffer. allocateDirect(10);          byte temp[]={2,3,4,5,6,8,1};  //定义 byte数组        buf.put(temp);  //向缓冲区中 写入一组数据                  buf.flip();  //重设缓冲区        System.out.println("缓冲区中的内容:");        while(buf.hasRemaining()){              int x=buf.get();              System.out.print(x+" ");          }      }  }  

运行结果:
缓冲区中的内容:
2 3 4 5 6 8 1 

二、通道
通道(Channel)可以用来读取和写入数据,通道类似于之前的输入输出,但是程序不会直接操作通道,所有的内容都是先读到或写入到缓冲区中,在通过缓冲区中取得或写入的。
通道与传统的流操作不同,传统的流操作分为输入流或输出流,而通道本身是双向的,既可以完成输入也可以完成输出。
Channel 本身是一个接口。
1、FileChannel
FileChannel 是Channel 的子类,可以进行文件的读/写操作。
使用通道进行读写操作:
代码:
package com.zsc.day02;import java.io.File;import java.io.FileInputStream;import java.io.FileOutputStream;import java.nio.ByteBuffer;import java.nio.channels.FileChannel;public class FileChannelTest {    public static void main(String[] args) throws Exception {        File f1 = new File("d:"+File.separator+"Hello.java");        File f2 = new File("d:"+File.separator+"outHello.java");        FileInputStream fis = null ;//文件输入流        FileOutputStream fos = null ;//文件输出流        fis = new FileInputStream(f1);//实例化输入流        fos = new FileOutputStream(f2);//实例化输出流        FileChannel fin = null ;//声明输入的通道对象        FileChannel fon = null ;//声明输出的通道对象        fin = fis.getChannel();//得到输入的文件通道        fon = fos.getChannel();//得到输出的文件通道        ByteBuffer buf = ByteBuffer.allocate(1024);//开辟缓冲        int temp = 0 ;//声明变量接收的内容        while((temp=fin.read(buf))!=-1){//如果没读到底            buf.flip();//重设缓冲区            fon.write(buf);//输出缓冲区            buf.clear();//清空缓冲区        }        fin.close();//关闭输入流        fon.close();//关闭输出流        fis.close();//关闭输入通道        fos.close();//关闭输出通道    }}

2、内存映射:

内存映射可以把文件映射到内存中,这样文件内的数据就可以用内存读/写指令来访问,而不是用IputStream 或OutputStream这样的I/O操作类,采用此种方式读取文件的速度是最快的。
java 中访问文件内容的4种方式。
    RandomAccessFile ,随机读取数据,此种访问速度最慢;
    FileInputStream,文件输入流,使用此种方式速度较慢;
    缓冲读取(例如 BufferedReader)此种访问速度较快;
    内存映射(MappedByteBuffer),使用此种方式读取速度最快。
    
代码:
package com.zsc.day02;import java.io.File;import java.io.FileInputStream;import java.nio.MappedByteBuffer;import java.nio.channels.FileChannel;public class ChannelTest {    public static void main(String[] args) throws Exception {        File file = new File("d:"+File.separator+"Hello.java");        FileInputStream fis = null ;    //文件输入流        fis = new FileInputStream(file);    //实例化输入流        FileChannel fin = null ;    //声明输入的通道对象        fin = fis.getChannel();        //得到输入文件通道        MappedByteBuffer mbb = null ;    //声明文件的内存映射        //将文件映射到内存中        mbb = fin.map(FileChannel.MapMode.READ_ONLY, 0, file.length());                byte data[] = new byte[(int)file.length()];        //开辟字节数组,接收数据        int foot = 0 ;         //定义下标        while(mbb.hasRemaining()){        //判断数否有数据            data[foot++] = mbb.get();    //取出数据        }        System.out.println(new String(data));    //显示输入的数据        fis.close();        //关闭输入流        fin.close();        //关闭输入通道    }}

运行结果:
public calss Hello{    public static void mian(String args[]){            System.out.println("hello");    }}

注意:尽管创建内存映射文件非常简单,但是如果使用MappedByteBuffer 写入数据就可能非常危险。因为仅仅是改变数组中的单个元素内容这样的操作简单,就有可能直接修改磁盘上的具体文件,因为修改数据与数据重新保存到磁盘是一样的。

三、文件锁:FileLock
在java 新io中提供了文件所得功能,这样当一个线程将文件锁定以后,其他线程是无法操作此文件的。要想进行文件的锁定,则要使用FileLock 类来完成,此类的对象需要依靠FileChannel 进行实例化。

文件锁定的两种方式:
    共享锁:允许多个线程进行文件的读取操作;
    独占所:只允许一个线程进行文件的读/写操作
    
文件锁定:
以下程序在运行时将文件进行独占锁定,这样其他线程在锁定的30庙内是无法对此文件进行读写操作的。
代码:
package com.zsc.day02;import java.io.File;import java.io.FileOutputStream;import java.nio.channels.FileChannel;import java.nio.channels.FileLock;public class FileLockTest {    public static void main(String[] args) throws Exception {        File file = new File("d:"+File.separator+"Hello.java");        FileOutputStream fos = null ;        fos = new FileOutputStream(file,true);        FileChannel fon = null ;        fon = fos.getChannel();        FileLock fl = fon.tryLock();        if(fl != null){            System.out.println(file.getName()+"文件锁定30秒。");            Thread.sleep(30000);            fl.release();            System.out.println(file.getName()+"文件解除锁定。");        }        fos.close();        fon.close();    }}


运行结果:
Hello.java文件锁定30秒。Hello.java文件解除锁定。

四、字符集:Charset
在java语言中所有的信息都是以UNICODE 进行编码的,但是在计算机的世界里面并不是但存在一种编码,而是多个,而且如果对编码处理不当,就有可能引起乱码的产生。在java的新IO包中提供了Charset 类来负责处理编码的问题,该类还包含了创建编码器(CharsetEncoder)和创建解码器(CharsetDecoder)的操作。
1、获取Charset 类的全部编码:
代码:
package com.zsc.day02;import java.nio.charset.Charset;import java.util.Iterator;import java.util.Map;import java.util.SortedMap;public class GetAllCharset {    public static void main(String[] args) {        SortedMap<String,Charset> all = null ;        //声明 SortedMap 集合        all = Charset.availableCharsets();        //获取全部编码        Iterator<Map.Entry<String,Charset>> iter = null ;    //声明 Iterator 对象        iter = all.entrySet().iterator();        //实例化 Iterator        while(iter.hasNext()){    //迭代输出            Map.Entry<String, Charset> me = iter.next();    //取出每一个Map.Entry            System.out.println(me.getKey()+"---->"+me.getValue());        //输出信息        }    }}

运行结果
Big5---->Big5Big5-HKSCS---->Big5-HKSCSEUC-JP---->EUC-JPEUC-KR---->EUC-KRGB18030---->GB18030GB2312---->GB2312GBK---->GBKIBM-Thai---->IBM-ThaiIBM00858---->IBM00858IBM01140---->IBM01140IBM01141---->IBM01141IBM01142---->IBM01142...

2、 解码编码操作:
代码:
package com.zsc.day02;import java.nio.ByteBuffer;import java.nio.CharBuffer;import java.nio.charset.CharacterCodingException;import java.nio.charset.Charset;import java.nio.charset.CharsetDecoder;import java.nio.charset.CharsetEncoder;public class CharsetEnDeTest {    public static void main(String[] args) throws Exception {        Charset latin1 = Charset.forName("ISO-8859-1");        CharsetEncoder encoder = latin1.newEncoder();        CharsetDecoder decoder = latin1.newDecoder();        //通过CharBuffer 类中的 wrap()方法,将一个字符串变为CharBuffer 类型        CharBuffer cb = CharBuffer.wrap("圣诞快乐");        ByteBuffer buf = encoder.encode(cb);        System.out.println(decoder.decode(buf));    }}

运行结果:
Exception in thread "main" java.nio.charset.UnmappableCharacterException: Input length = 1    at java.nio.charset.CoderResult.throwException(CoderResult.java:278)    at java.nio.charset.CharsetEncoder.encode(CharsetEncoder.java:798)    at com.zsc.day02.CharsetEnDeTest.main(CharsetEnDeTest.java:17)

注意:运行以后将以"ISO-8859-1"的编码方式显示中文,这样肯定是无法正常显示的,所以之后的解码操作都会造成乱码,一般CharsetEncoder 和CharsetDecoder 都经常使用在文件的读写上,已实现文件编码的转换功能。

五、Selector
在原来使用的IO和Socket 构造网络服务时,所有的网络服务将使用阻塞的方式进行客户端的连接,而如果使用新IO则可以构造一个非阻塞的网络服务。

下面使用Selector 创建一个非阻塞的服务器,此服务器向客户端返回当前的系统时间。
代码:
package com.zsc.day02;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.Date;import java.util.Iterator;import java.util.Set;public class DateServer {    public static void main(String[] args) throws Exception {    //所有异常抛出        int ports[] = {8000,8001,8002,8003,8004,8005,8006,8007,8008};    //定义一组连接端口        Selector selector = Selector.open();    //打开一个选择器        for(int i = 0 ;i<ports.length;i++){    //构造服务器的启动消息            ServerSocketChannel initSer = null ;    //声明CharSocketChannel            initSer = ServerSocketChannel.open();    //打开服务器套接字通道            initSer.configureBlocking(false);    //服务器配置为非阻塞            ServerSocket initSock = initSer.socket();    //检索与此通道关联的服务器套接字                        InetSocketAddress address = null ;    //表示监听地址            address = new InetSocketAddress(ports[i]);    //实例化绑定地址            initSock.bind(address);    //绑定地址                        //注册选择器,相当于使用accept() 方法接收            initSer.register(selector, SelectionKey.OP_ACCEPT);    //            System.out.println("服务器运行,在"+ports[i]+"端口监听。");    //        }        int keysAdd = 0  ;    //        while((keysAdd=selector.select())>0){    //接收一组SelectionKey            Set<SelectionKey> selectedKeys = selector.selectedKeys();    //选择一组键,相应的通道以为IO准备就绪            //取出全部生成的key            Iterator<SelectionKey> iter = selectedKeys.iterator();                            while(iter.hasNext()){    //迭代全部的key                SelectionKey key = (SelectionKey)iter.next();    //                if(key.isAcceptable()){    //取出每一个Selectionkey,判断客户端是否已经连接上                    ServerSocketChannel server = (ServerSocketChannel)key.channel();    //取得 Channel                    SocketChannel client = server.accept();        //接收新连接                    client.configureBlocking(false);    //设置非阻塞状态                    ByteBuffer outBuf = ByteBuffer.allocateDirect(1024);    //开辟缓冲去                    outBuf.put(("当前时间为:"+new Date()).getBytes());    //向缓冲区设置内容                    outBuf.flip();    //重置缓冲区                    client.write(outBuf);    //输出信息                    client.close();    //关闭输出流                }            }            selectedKeys.clear();    //清除全部的key        }    }}

运行结果:
以上程序运行后,程序将在8000,8001,8002,8003,8004,8005,8006,8007,8008 8 个端口进行监听进行服务器的监听,等待客户端连接,刻度连接后将返回系统的当前时间

在浏览器输入:http://127.0.0.1:8001
将返回:
当前时间为:Sat Dec 24 16:54:57 CST 2011
   




原创粉丝点击