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中,因此操做都是以缓冲区进行的,使操做的性能大大提升。java

操做

在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("一、写入数据以前的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("二、写入数据以后的position、limit和capacity:") ;
        System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;

        buf.flip() ;    // 重设缓冲区
        // postion = 0 ,limit = 本来position
        System.out.print("三、准备输出数据时的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的通道相似流,但又有些不一样:app

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

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

Channel的实现

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

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

FileChannel 从文件中读写数据。socket

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

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

相关文章
相关标签/搜索