java 新 IO(6)


问题:如果一个程序现在需要等待用户输入数据,则可以通过 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=10
2.写入数据之后:position=7,limit=10,capacity=10
3.准备输出数据时:position=0,limit=7,capacity=10
1 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---->Big5
Big5-HKSCS---->Big5-HKSCS
EUC-JP---->EUC-JP
EUC-KR---->EUC-KR
GB18030---->GB18030
GB2312---->GB2312
GBK---->GBK
IBM-Thai---->IBM-Thai
IBM00858---->IBM00858
IBM01140---->IBM01140
IBM01141---->IBM01141
IBM01142---->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
   




你可能感兴趣的:(java,exception,String,IO,buffer,import)