资讯专栏INFORMATION COLUMN

Java的NIO

mrcode / 869人阅读

摘要:也支持锁定部分内容,使用即可,其中为时,表明该锁是一个共享锁,可以允许多个县城读取文件,但阻止其他进程获得该文件的排他锁。当为时,表明是一个排他锁,它将锁住对该文件的读写。默认获取的是排他锁。

Java的NIO

BufferedReader有一个特征,就是读取输入流中的数据时,如果没有读到有效数据,程序将在此处阻塞该线程的执行(使用InputStream的read方法从流中读取数据时,也有这样的特性),java.io下面的输入输出流都是阻塞式的。不仅如此,传统的输入输出流都是通过字节的移动来处理的,及时我们不直接去处理字节流,单底层的实现还是依赖于字节处理,也就是说,面向流的输入输出系统一次只能处理一个字节,因此面向流的输入输出系统通常效率都不高。

为了解决上面的问题,NIO就出现了。NIO采用内存映射文件来处理输入输出,NIO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了。(这种方式模拟了操作系统上虚拟内存的概念),通过这种方式进行输入输出要快得多。

Channel(通道)和Buffer(缓冲)是NIO中两个核心对象。Channel是对传统的输入输出系统的模拟,在NIO系统中所有的数据都需要通过通道传输,Channel与传统的InputStream、OutputStream最大的区别就是提供了一个map()方法,通过它可以直接将“一块数据”映射到内存中。如果说传统IO是面向流的处理,那么NIO是面向块的处理。

Buffer可以被理解为一个容器,它的本质是一个数组,发送到Channel中所有的对象都必须首先放到Buffer中,从而Channel中读取的数据也必须先放到Buffer中。Buffer既可以一次次从Channel取数据,也可以使用Channel直接将文件的某块数据映射成Buffer。

除了Channel和Buffer之外,NIO还提供了用于将Unicode字符串映射成字节序列以及逆映射操作的Charset类,也提供了用于支持非阻塞式输入输出的Selector类

Buffer介绍

在Buffer中有三个重要的概念:

容量(capacity):表示Buffer的大小,创建后不能呢过改变;

界限(limit):第一个不能被读写的缓冲区的位置,也就是后面的数据不能被读写;

位置(position):用于指明下一个可以被读写的缓冲区位置索引。当从Channel读取数据的时候,position的值就等于读到了多少数据。

Buffer的主要作用就是装入数据,然后输出数据。当装入数据结束时,调用flip()方法,该方法将limit设为position所在位置,将position的值设为0,为输出数据做好准备。当输出数据结束后,调用clear()方法,将position的值设为0,limit设为capacity,为下一次的装入数据做准备。

常用的Buffer是CharBuffer和ByteBuffer。

使用put()和get()方法进行数据的放入和读取,分为相对和绝对两种:

相对:从Buffer当前position位置开始读取或者写入数据,然后将position的值按处理元素的个数增加;

绝对:直接根据索引向Buffer中读取和写入数据,使用绝对方式访问Buffer里的数据时,不会影响position的值。

代码示例

package com.wangjun.othersOfJava;

import java.nio.CharBuffer;

public class NIOBufferTest {

    public static void main(String[] args) {
        //创建CharBuffer
        CharBuffer cb = CharBuffer.allocate(8);
        System.out.println("capacity:" + cb.capacity());
        System.out.println("limit:" + cb.limit());
        System.out.println("position:" + cb.position());
        //放入元素
        cb.put("a");
        cb.put("b");
        cb.put("c");
        System.out.println("加入三个元素后,position:" + cb.position());
        cb.flip();
        System.out.println("执行flip()后,limit:" + cb.limit());
        System.out.println("执行flip()后,position:" + cb.position());
        System.out.println("取出第一个元素: " + cb.get());
        System.out.println("取出第一个元素后,position:" + cb.position());
        //调用clear方法
        cb.clear();
        System.out.println("执行clear()后,limit:" + cb.limit());
        System.out.println("执行clear()后,position:" + cb.position());
        System.out.println("执行clear()后,数据没有清空,第三个值是:" + cb.get(2));
        System.out.println("执行绝对读取后,position:" + cb.position());
    }

}

通过allocate方法创建的是普通Buffer,还可以通过allocateDirect方法来创建直接Buffer,虽然创建成本比较高,但是读写快。因此适用于长期生存的Buffer,使用方法和普通Buffer类似。注意,只有ByteBuffer提供了此方法,其他类型的想用,可以将该Buffer转成其他类型的Buffer。

Channel(通道)介绍

Channel类似传统的流对象,主要区别如下:

Channel可以直接将指定文件的部分或全部直接映射成Buffer。

程序不能直接访问Channel中的数据,只能通过Buffer交互。

所有的Channel都不应该通过构造器来创建,而是通过传统的InputStream、OutputStream的getChannel()方法来返回对应的Channel,不同的节点流获取的Channel不一样,比如FileInputStream返回的是FileChannel。

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

代码示例

package com.wangjun.othersOfJava;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.CharBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;

/*
 * 将FileChannel的全部数据映射成ByteBuffer
 */
public class NIOChannelTest {

    public static void main(String[] args) throws Exception {
        File f = new File("NIOChannelTest.md");
        //java7新特性try括号内的资源会在try语句结束后自动释放,前提是这些可关闭的资源必须实现 java.lang.AutoCloseable 接口。
        try(
            FileChannel inChannel = new FileInputStream(f).getChannel();
            FileChannel outChannel = new FileOutputStream("a.md").getChannel();
                ){
            //将FileChannel的全部数据映射成ByteBuffer
            //map方法的三个参数:1映射模式;2,3控制将哪些数据映射成ByteBuffer
            MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length());
            //直接将buffer的数据全部输出,完成文件的复制: NIOChannelTest.md -> a.md
            outChannel.write(buffer);
            
            //以下为了输出文件字符串内容
            //使用GBK字符集来创建解码器
            Charset charset = Charset.forName("GBK");
            //复原limit和position的位置
            buffer.clear();
            //创建解码器
            CharsetDecoder decoder = charset.newDecoder();
            //使用解码器将ByteBuffer转成CharBuffer
            CharBuffer charBuffer = decoder.decode(buffer);
            System.out.println(charBuffer);
        }
    }

}

除了上面的一次性取数据,也可以分多次取数据

//如果Channel对应的文件过大,使用map方法一次性将所有文件内容映射到内存中可能会因此性能下降
//这时候我们可以适应Channel和Buffer进行多次重复取值
public static void getDataByArray() throws Exception {
  try(
    //创建文件输入流
    FileInputStream fileInputStream = new FileInputStream("NIOChannelTest.md");
    FileChannel fileChannel = fileInputStream.getChannel();
  ){
    //定义一个ByteBuffer对象,用于重复取水
    ByteBuffer bbuff = ByteBuffer.allocate(64);
    while(fileChannel.read(bbuff) != -1) {
      bbuff.flip();
      Charset charset = Charset.forName("GBK");
      //创建解码器
      CharsetDecoder decoder = charset.newDecoder();
      //使用解码器将ByteBuffer转成CharBuffer
      CharBuffer charBuffer = decoder.decode(bbuff);
      System.out.println(charBuffer);
      //为下一次读取数据做准备
      bbuff.clear();
    }
  }
}
文件锁

在NIO中,Java提供了文件锁的支持,使用FileLock来支持文件锁定功能,在FileChannel中提供lock()/tryLock()方法来获取文件锁FileLock对象,从而锁定文件。lock和tryLock的区别是前者无法得到文件锁的时候会阻塞,后者不会阻塞。也支持锁定部分内容,使用lock(long position, long size, boolean shared)即可,其中shared为true时,表明该锁是一个共享锁,可以允许多个县城读取文件,但阻止其他进程获得该文件的排他锁。当shared为false时,表明是一个排他锁,它将锁住对该文件的读写。

默认获取的是排他锁。

代码示例

package com.wangjun.othersOfJava;

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 {
        try(
            FileOutputStream fos = new FileOutputStream("a.md");
            FileChannel fc = fos.getChannel();
                ){
            //使用非阻塞方式对指定文件加锁
            FileLock lock = fc.tryLock();
            Thread.sleep(3000);
            lock.release();//释放锁
        }
    }
}
Java的NIO2

Java7对原来的NIO进行了重大改进:

提供了全面的文件IO和文件系统访问支持;

基于异步Channel的IO。

这里先简单介绍一下对文件系统的支持,后续继续学习。

NIO2提供了一下接口和工具类:

Path接口:通过和Paths工具类结合使用产生Path对象,可以获取文件根路径、绝对路径、路径数量等;

Files工具类:提供了很多静态方法,比如复制文件、一次性读取文件所有行、判断是否为隐藏文件、判断文件大小、遍历文件和子目录、访问文件属性等;

FileVisitor接口:代表一个文件访问器,Files工具类使用walkFileTree方法遍历文件和子目录时,都会触发FileVisitor中相应的方法,比如访问目录之前、之后,访问文件时,访问文件失败时;

WatchService:监控文件的变化;

NIO和IO的区别

Java的NIO提供了与标准IO不同的工作方式:

Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,没有被缓存在任何地方,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中,因此可以前后移动流中的数据;

Asynchronous IO(异步IO):Java NIO可以让你异步的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。因为NIO将阻塞交给了后台线程执行。而IO是阻塞的;

Selectors(选择器):选择器允许一个多带带的线程可以监听多个数据通道((网络连接或文件),你可以注册多个通道使用一个选择器,然后使用一个多带带的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个多带带的线程很容易来管理多个通道。但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。 

使用场景

NIO

优势在于一个线程管理多个通道;但是数据的处理将会变得复杂;

如果需要管理同时打开的成千上万个连接,这些连接每次只是发送少量的数据,采用这种;

传统的IO

适用于一个线程管理一个通道的情况;因为其中的流数据的读取是阻塞的;

如果需要管理同时打开不太多的连接,这些连接会发送大量的数据;

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71093.html

相关文章

  • Java NIO 系列教程

    摘要:异步可以让你异步的使用,例如当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。因此,单个的线程可以监听多个数据通道。下面是系列文章的目录概述通道之间的数据传输与原文译者郭蕾校对方腾飞 Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的...

    fanux 评论0 收藏0
  • Java NIO 概览

    摘要:线程之间的切换对于操作系统来说是昂贵的。因此,单线程可以监视多个通道中的数据。当方法返回后,线程可以处理这些事件。 一 NIO简介 Java NIO 是 java 1.4 之后新出的一套IO接口,这里的的新是相对于原有标准的Java IO和Java Networking接口。NIO提供了一种完全不同的操作方式。 NIO中的N可以理解为Non-blocking,不单纯是New。 它支持面...

    chemzqm 评论0 收藏0
  • 关于Java IO与NIO知识都在这里

    摘要:从通道进行数据写入创建一个缓冲区,填充数据,并要求通道写入数据。三之通道主要内容通道介绍通常来说中的所有都是从通道开始的。从中选择选择器维护注册过的通道的集合,并且这种注册关系都被封装在当中停止选择的方法方法和方法。 由于内容比较多,我下面放的一部分是我更新在我的微信公众号上的链接,微信排版比较好看,更加利于阅读。每一篇文章下面我都把文章的主要内容给列出来了,便于大家学习与回顾。 Ja...

    Riddler 评论0 收藏0
  • Java NIO 前生今世 之一 简介

    摘要:简介是由引进的异步由以下几个核心部分组成和的对比和的区别主要体现在三个方面基于流而基于操作是阻塞的而操作是非阻塞的没有概念而有概念基于与基于传统的是面向字节流或字符流的而在中我们抛弃了传统的流而是引入了和的概念在中我只能从中读取数据到中或将 简介 Java NIO 是由 Java 1.4 引进的异步 IO.Java NIO 由以下几个核心部分组成: Channel Buffer Se...

    李义 评论0 收藏0
  • 动力节点JavaNIO教程,轻松攻破Java NIO技术壁垒

    摘要:学习和掌握技术已经不是一个攻城狮的加分技能,而是一个必备技能。是双向的,不仅可以读取数据还能保存数据,程序不能直接读写通道,只与缓冲区交互为了让大家不被高并发与大量连接处理问题所困扰,动力节点推出了高效处理模型应用教程。 大家肯定了解Java IO, 但是对于NIO一般是陌生的,而现在使用到NIO的场景越来越多,很多技术框...

    ralap 评论0 收藏0
  • Netty序章之BIO NIO AIO演变

    摘要:后改良为用线程池的方式代替新增线程,被称为伪异步。最大的问题是阻塞,同步。每次请求都由程序执行并返回,这是同步的缺陷。这些都会被注册在多路复用器上。多路复用器提供选择已经就绪状态任务的能力。并没有采用的多路复用器,而是使用异步通道的概念。 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能、高可靠的网络服务器和客户端程序。Netty简化了网络程序的开发,是很多框架和公司...

    VincentFF 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<