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命令完成,这样就完成了一个一部的操作服务器。

     

  • 相关阅读:
    Kmeans文本聚类系列(已经完成)
    提问的智慧
    公告:关于博客管理
    MSSQL语句备份
    贝叶斯分类专题
    利用Viterbi算法,二元词图分词系列
    从KL相对熵(relative entropy或 KullbackLeibler divergence,KL距离)看文本语义距离系列
    特征词选择算法对文本分类准确率的影响(五)
    新闻类网页正文提取系列
    特征词选择算法对文本分类准确率的影响(四)
  • 原文地址:https://www.cnblogs.com/oumyye/p/4323420.html
Copyright © 2011-2022 走看看