zoukankan      html  css  js  c++  java
  • java NIO 深入学习

    java NIO概述:

    1、在 JDK 1. 4 中 新 加入 了 NIO( New Input/ Output) 类, 引入了一种基于通道和缓冲区的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆的 DirectByteBuffer 对象作为这块内存的引用进行操作,避免了在 Java 堆和 Native 堆中来回复制数据。

    2、 NIO 是一种同步非阻塞的 IO 模型。同步是指线程不断轮询 IO 事件是否就绪,非阻塞是指线程在等待 IO 的时候,可以同时做其他任务。同步的核心就是 Selector,Selector 代替了线程本身轮询 IO 事件,避免了阻塞同时减少了不必要的线程消耗;非阻塞的核心就是通道和缓冲区,当 IO 事件就绪时,可以通过写道缓冲区,保证 IO 的成功,而无需线程阻塞式地等待

    NIO和IO的区别:

    1、 NIO是以块的方式处理数据,但是IO是以最基础的字节流的形式去读写数据,所以在效率上NIO比IO的效率高很多。

    2、 NIO不是和IO一样使用inputStream/outputStream的形式进行操作数据,但是又是基于这种流形式。同时又采用了通道和缓冲区的形式处理数据

    3、 还有一点。NIO是双向的,而IO是单向的

    4、 NIO的缓冲区(字节数组)还可以进行分片处理。可以建立直接缓冲区、非直接缓冲区、直接缓冲区是为了提高IO的速度。而以一种特殊的方式进行分配内存缓冲区

    以上几点可以直接概括为:IO是面向流的,NIO面向缓冲区的

    NIO的三个核心:

    1、缓冲区(buffer)

    在java NIO中负责数据的存取,缓冲区(buffer)本质就是一个数组,用于存储不同的数据类型,根据类型的不同(boolean)除外,提供了相依的类型缓冲区,分别为:

     * ByteBuffer

     * CharBuffer

     * ShortBuffer

     * IntBuffer

     * LongBuffer

     * FloatBuffer

     * DoubleBuffer

    其中(ByteBuffer)最为常用。

    上述的缓冲区管理的方式几乎一致,通过allocate();

    缓冲区存取数据的两个核心方法:

    put():存入数据到缓冲区

    get():获取缓冲区的数据

         缓冲区中的四个核心属性:

    capacity:容量,表示缓冲区最大的存储数量,一旦声明不能改变

    limit:界限,表示缓冲区可以操作数据的大小,limit后面的数据不能操作

    position:位置,表示缓冲区正在操作数据的位置。

    mark:标记,用于记录当前position的位置,可以使用rest()恢复到mark的位置。

    其中:capatity >= limit >= position >= mark

    2、通道(channel):

      概念:用于源节点与目标节点的连接。在 java NIO中负责缓存区中数据的传输,channel本身不存储数据,因此需要配合缓冲区进去传输。通道(channel)可以

          简单的理解为火车的轨道,火车相当是缓冲区。

         通道的主要类:

     *     java.nio.channels.Channel 接口:

     *         |--FileChannel

     *         |--Sockethannel

     *         |--ServerSocketChannel

     *         |--DatagramChannel

         获取管道:

     * 1、java针对支持通道的类提供了getChannel()方法

     *    本地IO:

     *    FileInputStream/FileOutputStream

     *    RandomAccessFile

     *   

     *    网络IO:

     *    Socket

     *    ServerSocket

     *    DatagramSocket

     * 2、在 JDK 1.7中的NIO.2针对各个通道提供了静态方法open()

     * 3、在 JDK 1.7中的NIO.2的Files 工具类的newByteChannel()

     通道之间的数据传输

     * transferFrom()

     * transfetTo()

     分散(Scatter)与聚集(Gather)

    * 分散读取(Scattering Reads) :将通道中的数据分散到多个缓冲区

    * 聚集写入(Gathering Writes):将多个缓冲区中的数据聚集到通道中

    3、选择器(selector)

    1、通道(channel)和缓冲区(Buffer)的机制,使得线程无阻塞的等待IO事件的就绪,但是要有人来监管它,这个时候就需要选择器(selector)完成,这就是所谓的同步。

    2、选择器(selector)允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便

    3、要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪,这就是所说的轮询。一旦这个方法返回,线程就可以处理这些事件。

    Selector中注册的感兴趣事件有:

    OP_ACCEPT

    OP_CONNECT

    OP_READ

    OP_WRITE

    优化:一种优化方式是:将Selector进一步分解为Reactor,将不同的感兴趣事件分开,每一个Reactor只负责一种感兴趣的事件。这样做的好处是:1、分离阻塞级别,减少了轮询的时间;2、线程无需遍历set以找到自己感兴趣的事件,因为得到的set中仅包含自己感兴趣的事件

    NIO缓冲区使用实例:

    @Test
        public void test() throws Exception
        {
            String str = "abcd";
            //创建缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
            //获取缓冲区的容量 capatity
            System.out.println("缓冲区的容量为:"+buf.capacity());
            //获取缓冲区的界限 limit
            System.out.println("缓冲区的界限为:"+buf.limit());
            //获取正则操作数据的位置 position
            System.out.println("缓冲区操作数据的位置:"+buf.position());
            System.out.println("----------------------------------------------");
            
            //向缓冲区添加数据
            buf.put(str.getBytes());
            //添加数据后注意观察 postion 的变化
            //获取缓冲区的容量 capatity
            System.out.println("缓冲区的容量为:"+buf.capacity());
            //获取缓冲区的界限 limit
            System.out.println("缓冲区的界限为:"+buf.limit());
            //获取正则操作数据的位置 position
            System.out.println("缓冲区操作数据的位置:"+buf.position());  
            
            System.out.println("-----------------------------------------------");
            
            //切换至读取模式     如果想要获取缓冲区中的数据需要切换至读模式
            buf.flip();
            //注意观察切换至读模式后 其 缓冲区的操作界限
            System.out.println("切换至读取模式缓冲区的界限为:"+buf.limit());
            byte [] b = new byte[buf.limit()];
            //使用get获取缓冲区中的数据
            buf.get(b,0,2);
            System.out.println(new String(b,0,2));
            //获取正则操作数据的位置 position
              System.out.println("缓冲区操作数据的位置:"+buf.position());  
            //使用mark标记位置
            buf.mark();
            buf.get(b,2,2);
            System.out.println(new String(b,2,2));
            System.out.println("缓冲区操作数据的位置:"+buf.position()); 
            
            //使用rest重置位置
            buf.reset();
            System.out.println("使用rest重置后缓冲区操作数据的位置:"+buf.position()); 
        }

    NIO管道(channel)实例(利用通道完成文件的复制(使用非直接缓冲区)

    @Test
        public void test01()   //1261   1244
        {
            long start = System.currentTimeMillis();
            FileInputStream ins = null;
            FileOutputStream out = null;
            
            //1、获取通道
            FileChannel inChannel = null;
            FileChannel outChannel= null;
            try
            {
                ins = new FileInputStream("C:\temp\test\1.zip");
                out = new FileOutputStream("C:\temp\test\2.zip");
                inChannel = ins.getChannel();
                outChannel = out.getChannel();
                
                //2、分配指定大小的缓冲区
                ByteBuffer buf = ByteBuffer.allocate(1024);
                //3、将通道中的数据存入缓冲区
                while(inChannel.read(buf) != -1)
                {
                    //切换读取数据模式
                    buf.flip();
                    //将缓冲区的数据写入通道
                    outChannel.write(buf);
                    //清空缓冲区
                    buf.clear();
                }
            }
            catch(Exception  e)
            {
                e.printStackTrace();
            }
            finally
            {
                if(outChannel != null)
                {
                    try {
                        outChannel.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                    }
                }
                
                if(inChannel != null)
                {
                    try {
                        inChannel.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                
                if(ins != null)
                {
                    try {
                        ins.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                
                if(out != null)
                {
                    try {
                        out.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
            long end = System.currentTimeMillis();
            System.out.println("耗时:"+(end - start));
        }

    NIO管道(channel)实例(利用通道完成文件的复制(使用直接缓冲区)

    @Test
        public void test02() throws Exception  
        {
            long start = System.currentTimeMillis();
            
            FileChannel inChannel = FileChannel.open(Paths.get("C:\temp\test\1.zip"), StandardOpenOption.READ);
            FileChannel outChannel = FileChannel.open(Paths.get("C:\temp\test\2.zip"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE);
            
            //内存映射文件
            MappedByteBuffer inMapperBuf = inChannel.map(MapMode.READ_ONLY, 0, inChannel.size());
            MappedByteBuffer outMapperBuf = outChannel.map(MapMode.READ_WRITE, 0, inChannel.size());
            
            //直接对缓冲区进行数据操作
            byte [] dst = new byte[inMapperBuf.limit()];
            //获取直接缓冲区的数据
            inMapperBuf.get(dst);
            //将获取到的数据写入内存映射文件中
            outMapperBuf.put(dst);
            
            outChannel.close();
            inChannel.close();
            
            long end = System.currentTimeMillis();
            System.out.println("耗时:"+(end - start));
        }

    总结:使用直接缓冲区完成文件复制的时间比非直接缓冲区快。

    NIO通道(channel)实例(使用分散读取,聚集写入)

    @Test
        public void test04() throws IOException
        {
            RandomAccessFile inRaf = new RandomAccessFile("C:\temp\test\1.zip", "rw");
    
            //获取通道   51845754   155537264
            FileChannel channel = inRaf.getChannel();
            int l = (int) (channel.size() % 3);
            int len = (int) Math.floor((channel.size() * 1.0) / 3.0);
            ByteBuffer [] bytes = new ByteBuffer[3];
            for(int i = 0; i < bytes.length; i++)
            {
                if(i == bytes.length+1)
                    len = len + l;
                bytes[i] = ByteBuffer.allocate(len);        
            }
            
            //分散读取
            channel.read(bytes);
            
            //切换数据读取模式
            for(ByteBuffer byteBuffer:bytes)
            {
                byteBuffer.flip();
            }
            
            //聚集写入
            RandomAccessFile outRaf = new RandomAccessFile("C:\temp\test\2.zip", "rw");
            FileChannel outChannel = outRaf.getChannel();
            outChannel.write(bytes);
            
            outRaf.close();
            inRaf.close();
            
        }

    NIO通道(channel)实例(SocketChannel和ServerSocketChannel)的使用:

    //客户端
        @Test
        public void client() throws Exception
        {
            //1、获取通道
            SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));
            FileChannel inChannel = FileChannel.open(Paths.get("C:\temp\test\1.zip"), StandardOpenOption.READ);
            //2、分配指定大小的缓冲区
            ByteBuffer dst = ByteBuffer.allocate(1024);
            
            //读取本地文件,并发送服务端
            while(inChannel.read(dst) > -1)
            {
                dst.flip();   //切换读取模式
                sChannel.write(dst);
                dst.clear();
            }
            
            //关闭通道
            inChannel.close();
            sChannel.close();
        }
    
    
    //服务端
        @Test
        public void server() throws Exception
        {
            //1、获取通道
            ServerSocketChannel ssChannel = ServerSocketChannel.open();
            FileChannel outChannel = FileChannel.open(Paths.get("C:\temp\test\2.zip"),StandardOpenOption.READ,StandardOpenOption.WRITE,StandardOpenOption.CREATE);
            
            //绑定连接
            ssChannel.bind(new InetSocketAddress(9898));
            
            //获取客户端连接通道
            SocketChannel sChannel = ssChannel.accept();
            
            //分配指定的缓冲区
            ByteBuffer buf = ByteBuffer.allocate(1024);
            
            //接收客户端的数据,并保存到本地
            while(sChannel.read(buf) > -1)
            {
                buf.flip();
                outChannel.write(buf);
                buf.clear();
            }
            
            //关闭通道
            sChannel.close();
            ssChannel.close();
            outChannel.close();
        }

    NIO选择器(selector)实例

    //客户端
        @Test
        public void client() throws Exception
        {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //获取通道
            SocketChannel sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",9898));
            
            //设置非阻塞模式
            sChannel.configureBlocking(false);
            
            //设置缓冲区大小
            ByteBuffer buf = ByteBuffer.allocate(1024);
            Scanner scan = new Scanner(System.in);
            while(scan.hasNext())
            {
                String str = scan.next();
                //System.out.println(str);
                buf.put((sdf.format(new Date())+"
    "+str).getBytes());
                //buf.put(str.getBytes());
                buf.flip();
                sChannel.write(buf);
                buf.clear();
            }
            sChannel.close();
            //scan.close();
        }
    
    //服务端
        @Test
        public void server() throws Exception
        {
            //1、获取通道
            ServerSocketChannel ssChannel = ServerSocketChannel.open();        
            //2、设置非阻塞模式
            ssChannel.configureBlocking(false);        
            //3、绑定连接
            ssChannel.bind(new InetSocketAddress("127.0.0.1",9898));        
            //4、获取选择器
            Selector selector = Selector.open();
            //5、将通道注册到选择器上,并指定“监听接收事件”
            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、取消选择键SelectorKey
                    it.remove();
                }    
            }
        }

    NIO实例 DatagrampChannel类的使用:

    在java NIO 中DatagramChannel 是一个只能收发UDP包的通道。因为UDP是一个无连接的网络协议,所以不能像其他通道一样读取和写入它发送和接收的是数据包

    代码示例:

    //发送数据包
        @Test
        public void send() throws Exception
        {
            DatagramChannel dc = DatagramChannel.open();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //设置无阻塞模式
            dc.configureBlocking(false);
            ByteBuffer buf = ByteBuffer.allocate(1024);
            
            Scanner scan = new Scanner(System.in);
            while(scan.hasNext())
            {
                String str = scan.next();
                buf.put((sdf.format(new Date())+"
    "+str).getBytes());
                buf.flip();
                dc.send(buf, new InetSocketAddress("127.0.0.1",9898));
                buf.clear();
            }
            dc.close();
        }
        
        //接收数据包
        @Test
        public void receive() throws Exception
        {
            DatagramChannel dc = DatagramChannel.open();
            //设置非阻塞模式
            dc.configureBlocking(false);
            dc.bind(new InetSocketAddress(9898));
            
            Selector selector = Selector.open();
            dc.register(selector, SelectionKey.OP_READ);
            while(selector.select() > 0)
            {
                Iterator<SelectionKey> it = selector.selectedKeys().iterator();
                while(it.hasNext())
                {
                    SelectionKey sk = it.next();
                    if(sk.isReadable())
                    {
                        ByteBuffer buf = ByteBuffer.allocate(1024);
                        //接收数据包
                        dc.receive(buf);
                        //切换至读模式
                        buf.flip();
                        System.out.println(new String(buf.array(),0,buf.limit()));
                        buf.clear();
                    }
                }
            }
        }
  • 相关阅读:
    pm2中文文档
    大前端技能图谱
    手把手教你用express搭建个人博客(二)
    javascript this讲解
    手把手教你用express搭建个人博客(一)
    使用国内手机号注册Google帐号的方法(2020-12-13亲测有效)
    常见浏览器修改User-Agent的方法
    Debian 9 Stretch国内常用镜像源
    Nginx核心模块内置变量
    tmux基本操作
  • 原文地址:https://www.cnblogs.com/wujiaofen/p/11431175.html
Copyright © 2011-2022 走看看