首页 > 其他 > 详细

NIO

时间:2019-08-30 20:21:22      阅读:64      评论:0      收藏:0      [点我收藏+]

NIO:

使用InputStream的read()方法从流中读取数据时,若数据源中没有数据,它会阻塞该线程,而OutputStream流,也是如此。因为它们都是阻塞式的输入与输出。不仅仅如此,传统的输入流,输出流都是通过字节的移动来处理的(即使不直接去处理字节流,但镀层的实现还是依赖于字节处理),也就是说,面向流的输入/输出,系统一次只能处理一个字节,因此面向流的输入/输出效率不高,为了提高效率,Java提供了一系列改进的输入/输出处理的新功能,这些新功能统称为New IO,简写为NIO。

NIO使用了不同的方式来处理输入/输出,它采用内存映射文件的方式来处理输入/输出,NIO将文件或者文件的一段区域映射到了内存中,这样就可以想访问内存一样来访问文件了,通过这种方式来进行输入/输出比传统的输入/输出要快得多。

在NIO中,有两个核心对象,一个是Channel (通道),另外一个则是Buffer(缓存)。

Channel是对传统的输入/输出系统的模拟,在NIO系统中,所有的数据都需要通过Channel来进行传输(包括输出与输入),与传统的InputStream和OutputStream相比,它提供了一个map()方法,通过该map方法可以直接将“一块数据”映射到内存中。

但程序是不能直接访问Channel中的数据,包括读取,写入都不行,Channel只与Buffer进行交互。也就是说,如果要从Channel中取得数据,就必须先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据;同理,若要对通道写入东西,也是先写入Buffer中,然后将Buffer写入Channel中。

Channel是一个抽象基类,按功能可以分成以下几大类:

  1. 用于文件的Channel: FileChannel

  2. 用于TCP网络的Channel: ServerSocketChannel,SocketChannel

  3. 用于UDP网络的Channel:DatagramChannel

  4. 用于支持线程间通信的Channel:Pipe.SinkChannel,PipeSourceChannel

  以上的Channel都不能通过构造方法来直接创建,而是调用InputStream/OutputStream的getChannel()方法来返回对应的Channel,根据不同的节点流返回的Channel也不一样,比如说FileInputStream/FileOutputStream返回的就是FileChannel。

 

Channel中最常用的三类方法,map(),read(),write(),其中map()方法用于将Channel对应的部分或者全部数据映射成了ByteBuffer;而read()方法与write()方法都有一系列重载形式,这些方法用于从Bufer中读取数据或向Buffer中写入数据。

  关于map()方法中,有三个参数,第一个是FileChannel.MapMode参数,该参数执行映射时的模式,有只读,读写等模式,该FileChannel.MapMode下有对应常量,调用对应的常量作为参数便可;第二个,第三个参数用于控制将Channel的那些数据映射成ByteBuffer。

下面通过NIO来实现拷贝文件:

public class NIO {
    public void copyFile(String path ,String copyPath){
        File f1 = new File(path);
        File f2 = new File(copyPath);
        try{
            if(!f1.exists()){
                System.out.println("拷贝文件不存在");
            }
            if(!f2.exists()){
                f2.createNewFile();
            }
            if(f1.isFile() && f2.isFile()){
                FileChannel inChannel = new FileInputStream(f1).getChannel();
                FileChannel outChannel = new FileOutputStream(f2).getChannel();
                
                
                //一次性取完,用于小文件的拷贝
//                MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f1.length());
//                System.out.println(buffer);
//                outChannel.write(buffer);
//                System.out.println(buffer);
                
                //分段取数据,用于大文件的拷贝
                ByteBuffer byteBuff = ByteBuffer.allocate(1024);
                while(inChannel.read(byteBuff) != -1){
                    //锁定空白区
                    byteBuff.flip();
                    outChannel.write(byteBuff);
                    //释放空白区
                    byteBuff.clear();
                }
        }catch(Exception e){}    
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        NIO nio = new NIO();
        
        String charPath = "D:\\data\\file\\book.txt";
        String charCopyPath = "D:\\data\\file\\book_1.txt";
        nio.copyFile(charPath, charCopyPath);
    }
}

在上述代码中,有注释的适用于小文件的拷贝,另外一种是适用于大文件的拷贝

 

NIO的随机访问文件

在IO流中,有种可以任意访问文件位置并且可以在任意位置写入的RandomAccess流;在NIO中,也可以通过Buffer和Channel来实现。

public class NIO {
    
    //下面代码实现了将文件内容进行复制,追加在该文件后面
    public void RandomFileChannelTest(String path){
        File file = new File(path);
        if(!file.exists()){
            System.out.println("要拷贝文件的路径不存在");
        }else{
            try{
                RandomAccessFile raf = new RandomAccessFile(file,"rw");
                FileChannel fileChannel = raf.getChannel();
                ByteBuffer buffer =fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, file.length());
                fileChannel.position(file.length()); //FileChannel.position(),改变记录指针值
                fileChannel.write(buffer);
            }catch(Exception e){}
        }
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        NIO nio = new NIO();
        String path = "D:\\data\\file\\book_1.txt";
        nio.RandomFileChannelTest(path);
    }
}

 

字符集与Charset:

计算机底层是没有文本文件和图片之分的,它只是忠实地记录每个文件的二进制序列而已。对于文本文档而言,当需要保存时,程序/系统需要将文件的每个字符翻译成二进制序列;当需要读取时,程序/系统便会需要将二进制序列转换为一个个的字符。

但由于各个国家的语言不同,各个国家都有自己的一套字符集,也就是说,当我们将中国的字符集翻译成了二进制序列,在用这个二进制序列翻译成了其他国家的字符集,那么便会出来乱码。

在Java中,提供Charset类来处理字节序列和字符序列之间的转换关系。该类中,包含用于创建解码器和码编器的方法,还提供了获取Charset所支持的字符集方法。

public class NIO {
    public void charseTest(){
        //获取Java支持的全部字符集
        SortedMap<String ,Charset> map = Charset.availableCharsets();
        for(String str : map.keySet()){
            System.out.println(str +"------>"+map.get(str));
        }
    }
    
    public static void main(String[] args) {
        nio.charseTest();
    }
}

 对于中国而言,我们常用以下的字符集:

  GBK:简体中文字符集

  BIG5: 繁体中文字符集

  ISO-8859-1: ISO拉丁字母表NO.1,也叫做ISO-LATIN-1

  UTF-8 :8位UCS转换格式

  UTF-16BE:16位UCS转换格式

  UTF-16:16位UCS转换格式

  UTF-16LE:16位UCS转换格式

 

Charset不能通过构造方法进行实例,而是通过Charset.forName(String 字符集)的方法来获取实例。获取实例后,便可以调用newDecoder()/newEncoder()方法获取CharsetDecoder/CharsetEncoder对象,分别代表着Charset的解码器与编码器。

调用CharsetDecoder的decode()方法可以将ByteBuffer(字节序列)转换为CharBuffer(字符序列),调用CharsetEncode的encode()可以将CharBuffer(字符序列)转化为ByteBuffer(字节序列):

public class NIO {
    public void charseTransform(){
        CharBuffer chars = CharBuffer.allocate(8);
        chars.put("你");
        chars.put("是");
        chars.put("谁");
        chars.flip();
        
        Charset charset = Charset.forName("utf-8");
        CharsetDecoder decoder = charset.newDecoder();
        CharsetEncoder encoder = charset.newEncoder();
        try{
            ByteBuffer bytes = encoder.encode(chars);
            System.out.println("\""+decoder.decode(bytes)+"\"的编码如下:");
            for(int i=0;i<bytes.capacity();i++){
                System.out.print(bytes.get(i)+"\t");
            }            
        }catch(Exception e){}
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        NIO nio = new NIO();
        nio.charseTransform();
    }
}

运行效果如下:

"你是谁"的编码如下:
-28    -67    -96    -26    -104    -81    -24    -80    -127    

 

文件锁:

在操作系统中,如果多个运行程序需要并发修改同一个文件时,程序之间需要某种机制来进行通信,而使用文件锁可以有效地阻止多个进程并发修改同一个文件。

在Java中,提供了FileLock来支持文件锁定的功能,在FileChannel中提供的lock()/tryLock()方法可以获得文件锁的FileLock对象,从而锁定文件。

  lock和tryLock的区别在于:当lock()试图锁定某个文件时,如果无法得到文件锁,程序将一直阻塞;而tryLock()是尝试锁定文件,它将直接返回而不是阻塞,如果获得了文件锁,则返回该文件锁,若没有获得,则返回null。

  lock()/lock(long position ,long size,boolean shared)方法:对文件进行加锁/对文件从position 开始,长度为size的内容加锁,该方法时阻塞式的

  tryLock/tryLock(long position ,long size,boolean shared)方法:对文件进行加锁/对文件从position 开始,长度为size的内容加锁,该方法时非阻塞式的

  上述方法有参数的情况下,当参数shared为true时,表明该锁时共享锁,可以运行多个进程来读取该文件,但组织其他进程来获得对该文件的排他锁;当参数shared为false时,表明该锁是一个排他锁。它将锁住对该文件的读写。

 

处理完文件后,记得使用FileLock()的release()方法来释放文件锁。下面程序示范了使用FileLock锁定文件的示例:

public class NIO {
    public void fileLock(){
        try{
            FileChannel fileChannel = new FileOutputStream("out.txt").getChannel();    
            FileLock fileLock = fileChannel.tryLock();
            System.out.println("对当前目录下out.txt文件添加文件锁");
            Thread.sleep(3600000); //持续一分钟
            fileLock.release();
            System.out.println("对当前目录下out.txt文件释放文件锁");
        }catch(Exception e){}
    }
    
    public static void main(String[] args) {
        nio.fileLock();
    }
}

运行上述代码后,我们对当前目录下得out.txt进行编辑,保存时,提示另一个程序正在使用此文件,进程无法访问,说明上述程序已经对该文件上锁了,其他进程在上述程序执行完毕之前是无法对该文件后进行操作得。

                                                         技术分享图片 

 

 

关于文件锁有几点需要指出:

 1. 在某些平台上,文件锁仅仅是建议性,并不是强制性的。着意味着即使一个程序不能获得文件锁,它也可以对该文件进行读写。

 2. 在某些平台上,不能同步地锁定一个文件并把它映射到内存中

 3. 文件锁是由Java虚拟机所持有的,如果两个Java程序使用同一个Java虚拟机运行,则它们不能对同一个文件进行加锁

 4. 在某些平台上关闭FileChannel时,会释放Java虚拟机在该文件上的所有锁,因此应该避免对同一个被锁定的文件打开多个FileChannel。

 

NIO

原文:https://www.cnblogs.com/hjlin/p/11436770.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!