NIO

简介

  • NIO与原来的IO有同样的作用和目的,但是使用方式完全不同,NIO支持面向缓存区的、基于通道的IO操作。NIO将以更加高效的方式进行文件的读写操作。

java NIO 与 IO的主要区别

IO NIO
面向流 面向缓存区
阻塞IO(Blocking IO) 非阻塞IO(Non Blocking IO)
(无) 选择器
  • Channel 负责传输
  • Buffer 负责存储

缓存区存取数据的核心方法

  • put() 写

  • get() 读

  • flip() 切换模式

缓存区的四个核心属性

  • private int mark = -1; //标记,表示当前position的位置。可以通过reset()恢复到mark的位置

  • private int position = 0; //位置,表示缓存区正在操作数据的位置。

  • private int limit; //界限,表示缓存区可以操作数据的大小。(limit后数据不能进行读写)

  • private int capacity; //容量,表示缓存区的最大存储数据的容量。一旦声明不能改变。

  • 0 <= mark <= position <= limit <= capacity

public class test1 {

    public static void main(String[] args) {
        test2();
    }
    public static void test2(){
        String str = "abcde";

        //1. 分配一个指定大小的缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);

        System.out.println("----------allocate()---------");
        System.out.println(buf.position());//0
        System.out.println(buf.limit());//1024
        System.out.println(buf.capacity());//1024

        //2. 利用put()存入数据到缓冲区中
        buf.put(str.getBytes());

        System.out.println("----------put()---------");
        System.out.println(buf.position());//5
        System.out.println(buf.limit());//1024
        System.out.println(buf.capacity());//1024

        //3.切换读取数据模式
        buf.flip();

        System.out.println("----------put()---------");
        System.out.println(buf.position());//0
        System.out.println(buf.limit());//5
        System.out.println(buf.capacity());//1024

        //4.利用get()
        byte[] dst = new byte[buf.limit()];
        buf.get(dst);
        System.out.println(new String(dst,0,dst.length));

        System.out.println("----------get()---------");
        System.out.println(buf.position());//5
        System.out.println(buf.limit());//5
        System.out.println(buf.capacity());//1024

        //5.可重复读
        buf.rewind();
        System.out.println("----------rewind()---------");
        System.out.println(buf.position());//0
        System.out.println(buf.limit());//5
        System.out.println(buf.capacity());//1024

        //6.clear():清空缓冲区,但是缓冲区中的数据依然存在,但是出于“被遗忘”状态
        buf.clear();
        System.out.println("----------clear()---------");
        System.out.println(buf.position());//0
        System.out.println(buf.limit());//1024
        System.out.println(buf.capacity());//1024

        //mark:标记,表示记录当前position的位置。可以通过reset()恢复到mark位置
    }
}

直接缓冲区和非直接缓冲区

非直接缓存区:allocate()方法分配缓存区,将缓存区建立在JVM的内存中(堆,数组)

直接缓存区:allocateDirect()方法分配直接缓存区,将缓存区建立在物理内存中,可以提高效率

  • 非直接缓冲区


    image.png
  • 直接缓冲区


    image.png

Channel

  • Channel表示IO源与目标打开的连接。Channel类似于传统的“流”。只不过Channel本身不能直接访问数据,Channel只能与Buffer进行交互。
  • 分类:
    • FileChannel

    • SelectableChannel

      • SocketChannel
      • SockeServerChannel
      • DatagramChannel
      • Pipe.SinkChannel
      • Pipe.SourceChannel
  • 获取通道
  1. Java针对支持通道的类提供了getChannel()方法:
  • 本地IO:
    • FileInputStream/FileOutputStream
    • RamdomAccessFile
  • 网络IO:
    • Socket
    • ServerSocket
    • DatagramSocket
  1. 在JDK 1.7中的NIO.2 针对各个通道提供了静态方法open()
  2. 在JDK 1.7中的NIO.2 的Files工具类的newByteChannel()
  • 利用通道完成文件的复制(非直接缓冲区)
public class FileCopy {

    public static void main(String[] args) {
        test();
    }

    public static void test(){
        FileInputStream fis = null;
        FileOutputStream fos = null;

        //获取通道
        FileChannel inchannel = null;
        FileChannel outChannel = null;

        try {
            fis = new FileInputStream("D:/1.jpg");
            fos = new FileOutputStream("D:/2.jpg");

            inchannel = fis.getChannel();
            outChannel = fos.getChannel();

            //分配指定大小的缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);

            //将通道中的数据存入到缓冲区中
            while (inchannel.read(buf) != -1){
                buf.flip(); //切换到读数据的模式
                //将缓冲区的数据写入到通道中
                outChannel.write(buf);
                buf.clear();//清空缓存区
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally{
            if(inchannel != null){
                try {
                    inchannel.close();
                }catch (IOException e){
                    e.printStackTrace();
                }
            }

            if(outChannel != null){
                try {
                    outChannel.close();
                }catch (IOException e){
                    e.printStackTrace();
                }
            }

            if(fis != null){
                try {
                    fis.close();
                }catch (IOException e){
                    e.printStackTrace();
                }
            }

            if(fos != null){
                try {
                    fos.close();
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
}
  • 使用直接缓冲区完成文件的复制(内存映射文件)
public class FileCopy2 {

    public static void main(String[] args) {
        test3();
    }

    private static void test3() {

        try {
            FileChannel inChannel = FileChannel.open(Paths.get("D:/1.jpg"), StandardOpenOption.READ);
            FileChannel outChannel = FileChannel.open(Paths.get("D:/3.jpg"), StandardOpenOption.WRITE, StandardOpenOption.READ,
            StandardOpenOption.CREATE_NEW);

            //内存映射文件
            MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

            //直接对缓冲区进行数据的读写操作
            byte[] dst = new byte[inMappedBuf.limit()];
            inMappedBuf.get(dst);
            outMappedBuf.put(dst);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

分散(Scatter)和聚集(Gather)

  • 分散读取(Scattering Reads): 将通道中的数据分散到多个缓冲区中
  • 聚集写入(Gathering Writes): 将多个缓冲区中的数据聚集到通道中
public class FileCopy3 {

    public static void main(String[] args) {
        test4();
    }

    private static void test4() {
        try {
            RandomAccessFile raf1 = new RandomAccessFile("D:/3.txt","rw");

            //1.获取通道
            FileChannel channel1 = raf1.getChannel();

            //2.分配指定大小的缓冲区
            ByteBuffer buf1 = ByteBuffer.allocate(1);
            ByteBuffer buf2 = ByteBuffer.allocate(1024);

            //3.分散读取
            ByteBuffer[] bufs = {buf1,buf2};
            channel1.read(bufs);

            for (ByteBuffer byteBuffer : bufs) {
                byteBuffer.flip();
            }

            //聚集写入
            RandomAccessFile raf2 = new RandomAccessFile("D:/7.txt","rw");
            FileChannel channel2 = raf2.getChannel();
            channel2.write(bufs);


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

Selector

  • 是SelectableChannel的多路复用器,用于监控SelectableChannel的状态.它是实现NIO非阻塞的关键。
  • fileChannel不能切换成非阻塞式模式。
Snipaste_2021-03-17_16-25-30.png

阻塞式网络IO实现

  • 客户端
public class FileTest {

    public static void main(String[] args) {
        client();
    }

    //客户端
    private static void client() {

        try {
            //1.获取通道
            SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("9.236.37.131", 9898));

            FileChannel inChannel = FileChannel.open(Paths.get("D:/1.jpg"), StandardOpenOption.READ);

            //2.分配指定大小的缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);

            //3.读取本地文件,并发送到服务端
            while (inChannel.read(buf) != -1){
                buf.flip();
                sChannel.write(buf);
                buf.clear();
            }
            //单向关闭自己的输出流,并未关闭连接,不写这个服务器端并不知道传输的图片结束了。一直卡在ssChannel.accept()
            sChannel.shutdownOutput();

            //接收服务器的反馈
            int len = 0;
            while ((len = sChannel.read(buf))!=-1){
                buf.flip();
                System.out.println(new String(buf.array(),0,len));
                buf.clear();
            }

            //4.关闭通道
            inChannel.close();
            sChannel.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
  • 服务端
public class FileTest2 {

    public static void main(String[] args) {
        server();
    }

    //服务端
    private static void server() {
        try {
            //1.获取通道
            ServerSocketChannel ssChannel = ServerSocketChannel.open();

            FileChannel outChannel = FileChannel.open(Paths.get("D:/10.jpg"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

            //2.绑定链接
            ssChannel.bind(new InetSocketAddress(9898));

            //3.获取客户端连接的通道
            SocketChannel sChannel = ssChannel.accept();

            //4.分配指定大小的缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);

            //5.接收客户端的数据,并保存到本地
            while (sChannel.read(buf)!= -1){
                buf.flip();
                outChannel.write(buf);
                buf.clear();
            }

            //发送反馈给客户端
            buf.put("服务器接收数据成功".getBytes());
            buf.flip();
            sChannel.write(buf);
            //6.关闭通道
            sChannel.close();
            outChannel.close();
            ssChannel.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

非阻塞式网络IO实现

  • 客户端
public class NonBlockClient {

    public static void main(String[] args) {
        client();
    }

    private static void client() {

        try {
            //1.获取通道
            SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("9.236.37.131", 9898));

            //2.切换非阻塞模式
            sChannel.configureBlocking(false);

            //3.分配指定大小的缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);

            //4.发送数据给服务器
            Scanner scan = new Scanner(System.in);

            while (scan.hasNext()){
                String str = scan.next();
                buf.put((new Date().toString() + "\n"+str).getBytes());
                buf.flip();
                sChannel.write(buf);
                buf.clear();
            }

            //5.关闭通道
            sChannel.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 服务端
public class NonBlockServer {

    public static void main(String[] args) {
        server();
    }

    private static void server() {

        try {
            //1.获取通道
            ServerSocketChannel ssChannel = ServerSocketChannel.open();

            //2.切换非阻塞模式
            ssChannel.configureBlocking(false);

            //3.绑定链接
            ssChannel.bind(new InetSocketAddress(9898));

            //4.获取选择器
            Selector selector = Selector.open();

            //5.将通道注册到选择器上,并且指定“监听接收事件”
            //SelectionKey-----OP_ACCEPT,OP_CONNECT,OP_READ,OP_WRITE
            ssChannel.register(selector, SelectionKey.OP_ACCEPT);

            //6.轮询式的获取选择器上已经“准备就绪”的事件
            while (selector.select()>0){

                //7.获取当前选择器中所有注册的“选择键(已就绪的监听事件)”
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();

                while (it.hasNext()){
                    //8. 获取准备“就绪”的事件
                    SelectionKey sk = it.next();

                    //9.判断具体是什么事件准备就绪
                    if(sk.isAcceptable()){
                        //10. 若“接收就绪”,获取客户端连接
                        SocketChannel sChannel = ssChannel.accept();

                        //11.切换非阻塞模式
                        sChannel.configureBlocking(false);

                        //12.将该通道注册到选择器上
                        sChannel.register(selector,SelectionKey.OP_READ);
                    }else if(sk.isReadable()){
                        //13. 获取当前选择器上“读就绪”状态的通道
                        SocketChannel sChannel = (SocketChannel)sk.channel();

                        //14. 读取数据
                        ByteBuffer buf = ByteBuffer.allocate(1024);

                        int len = 0;
                        while ((len = sChannel.read(buf))>0){
                            buf.flip();
                            System.out.println(new String(buf.array(),0,len));
                            buf.clear();
                        }
                    }

                    //15.取消选择键SelectionKey
                    it.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • DatagramChannel --UDP用法类似
  • Pipe


    Snipaste_2021-03-17_16-54-55.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容