zoukankan      html  css  js  c++  java
  • java基础篇---新I/O技术(NIO)

    在JDK1.4以前,I/O输入输出处理,我们把它称为旧I/O处理,在JDK1.4开始,java提供了一系列改进的输入/输出新特性,这些功能被称为新I/O(NEW I/O),新添了许多用于处理输入/输出的类,这些类都被放在java.nio包及子包下,并且对原java.io包中的很多类以NIO为基础进行了改写,新添了满足新I/O的功能。

    Java NIO和IO的主要区别

    IO  NIO
    面向流
    面向缓冲
    阻塞IO 
     非阻塞IO
    选择器

    面向缓冲(Buffer)

    在整个Java的心I/O中,所以操作都是以缓冲区进行的,使操作的性能大大提高。

    操作

    在Buffer中存在一系列的状态变量,这状态变量随着写入或读取都可能会被概念,在缓冲区开元使用是三个值表示缓冲区的状态。

    • position:表示下个缓冲区读取或写入的操作指针,没向缓冲区中华写入数据的时候 此指针就会改变,指针永远放在写入的最后一个元素之后。即:如果写入了4个位置的数据,则posotion会指向第5个位置。
    • Limit:表示还有多少数据可以存储或读取,position<=limit
    • capacity:表示缓冲区的最大容量,limit<=capacity,此值在分配缓冲区时被设置。一般不改变。

    创建缓冲区:

    import java.nio.IntBuffer ;
    public class IntBufferDemo{
        public static void main(String args[]){
            IntBuffer buf = IntBuffer.allocate(10) ;    // 准备出10个大小的缓冲区
            System.out.print("1、写入数据之前的position、limit和capacity:") ;
            System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;
            int temp[] = {5,7,9} ;// 定义一个int数组
            buf.put(3) ;    // 设置一个数据
            buf.put(temp) ;    // 此时已经存放了四个记录
            System.out.print("2、写入数据之后的position、limit和capacity:") ;
            System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;
    
            buf.flip() ;    // 重设缓冲区
            // postion = 0 ,limit = 原本position
            System.out.print("3、准备输出数据时的position、limit和capacity:") ;
            System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;
            System.out.print("缓冲区中的内容:") ;
            while(buf.hasRemaining()){
                int x = buf.get() ;
                System.out.print(x + "、") ;
            }
        }
    }

    如果创建了缓冲区,则JVM可直接对其执行本机的IO操作

    import java.nio.ByteBuffer ;
    public class ByteBufferDemo{
        public static void main(String args[]){
            ByteBuffer buf = ByteBuffer.allocateDirect(10) ;    // 准备出10个大小的缓冲区
            byte temp[] = {1,3,5,7,9} ;    // 设置内容
            buf.put(temp) ;    // 设置一组内容
            buf.flip() ;
    
            System.out.print("主缓冲区中的内容:") ;
            while(buf.hasRemaining()){
                int x = buf.get() ;
                System.out.print(x + "、") ;
            }
        }
    }

    通道(Channel)

    Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

    Java NIO的通道类似流,但又有些不同:

    • 既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。
    • 通道可以异步地读写。
    • 通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

    正如上面所说,从通道读取数据到缓冲区,从缓冲区写入数据到通道。

    Channel的实现

    这些是Java NIO中最重要的通道的实现:

    • FileChannel
    • DatagramChannel
    • SocketChannel
    • ServerSocketChannel

    FileChannel 从文件中读写数据。

    DatagramChannel 能通过UDP读写网络中的数据。

    SocketChannel 能通过TCP读写网络中的数据。

    ServerSocketChannel可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。

    通过通道可以完成双向的输入和输出操作。在通道还有一种方式称为内存映射

    几种读入的方式的比较

    RandomAccessFile   较慢
    
    FileInputStream     较慢
    
    缓冲读取      速度较快
    内存映射      速度最快

    FileChannel内存映射实例

    import java.nio.ByteBuffer ;
    import java.nio.MappedByteBuffer ;
    import java.nio.channels.FileChannel ;
    import java.io.File ;
    import java.io.FileOutputStream ;
    import java.io.FileInputStream ;
    public class FileChannelDemo03{
        public static void main(String args[]) throws Exception{
            File file = new File("d:" + File.separator + "oumyye.txt") ;  
            FileInputStream input = null ;
            input = new FileInputStream(file) ;
            FileChannel fin = null ;    // 定义输入的通道
            fin = input.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)) ;    // 输出内容
            fin.close() ;
            input.close() ;
        }
    }

    操作以上代码的时候,执行的是写入操作则可能是非常危险的,因为仅仅只是改变数组中的单个元素这种简单的操作,就可能直接修改磁盘上的文件,因为修改数据与数据保存在磁盘上是一样的。

    选择器(Selectors

    Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

    为什么使用Selector?

    仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。

    但是,需要记住,现代的操作系统和CPU在多任务方面表现的越来越好,所以多线程的开销随着时间的推移,变得越来越小了。实际上,如果一个CPU有多个内核,不使用多任务可能是在浪费CPU能力。不管怎么说,关于那种设计的讨论应该放在另一篇不同的文章中。在这里,只要知道使用Selector能够处理多个通道就足够了。

    要点

    使用Selector可以构建一个非阻塞的网络服务。

    在新IO实现网络程序需要依靠ServerSocketChannel类与SocketChannel

    Selector实例

    下面使用Selector完成一个简单的服务器的操作,服务器可以同时在多个端口进行监听,此服务器的主要功能是返回当前时间。

    import java.net.InetSocketAddress ;
    import java.net.ServerSocket ;
    import java.util.Set ;
    import java.util.Iterator ;
    import java.util.Date ;
    import java.nio.channels.ServerSocketChannel ;
    import java.nio.ByteBuffer ;
    import java.nio.channels.SocketChannel ;
    import java.nio.channels.Selector  ;
    import java.nio.channels.SelectionKey  ;
    public class DateServer{
        public static void main(String args[]) throws Exception {
            int ports[] = {8000,8001,8002,8003,8005,8006} ; // 表示五个监听端口
            Selector selector = Selector.open() ;    // 通过open()方法找到Selector
            for(int i=0;i<ports.length;i++){
                ServerSocketChannel initSer = null ;
                initSer = ServerSocketChannel.open() ;    // 打开服务器的通道
                initSer.configureBlocking(false) ;    // 服务器配置为非阻塞
                ServerSocket initSock = initSer.socket() ;
                InetSocketAddress address = null ;
                address = new InetSocketAddress(ports[i]) ;    // 实例化绑定地址
                initSock.bind(address) ;    // 进行服务的绑定
                initSer.register(selector,SelectionKey.OP_ACCEPT) ;    // 等待连接
                System.out.println("服务器运行,在" + ports[i] + "端口监听。") ;
            }
            // 要接收全部生成的key,并通过连接进行判断是否获取客户端的输出
            int keysAdd = 0 ;
            while((keysAdd=selector.select())>0){    // 选择一组键,并且相应的通道已经准备就绪
                Set<SelectionKey> selectedKeys = selector.selectedKeys() ;// 取出全部生成的key
                Iterator<SelectionKey> iter = selectedKeys.iterator() ;
                while(iter.hasNext()){
                    SelectionKey key = iter.next() ;    // 取出每一个key
                    if(key.isAcceptable()){
                        ServerSocketChannel server = (ServerSocketChannel)key.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
            }
            
        }
    }

    服务器完成之后可以使用Telnet命令完成,这样就完成了一个一部的操作服务器。

     

  • 相关阅读:
    LeetCode "Jump Game"
    LeetCode "Pow(x,n)"
    LeetCode "Reverse Linked List II"
    LeetCode "Unique Binary Search Trees II"
    LeetCode "Combination Sum II"
    LeetCode "Divide Two Integers"
    LeetCode "First Missing Positive"
    LeetCode "Clone Graph"
    LeetCode "Decode Ways"
    LeetCode "Combinations"
  • 原文地址:https://www.cnblogs.com/oumyye/p/4323420.html
Copyright © 2011-2022 走看看