Java NIO之学习综述

目录

 

简介

缓冲区(Buffer)

通道(Channel)

选择器(Selector)

简单案例

总结


简介

为提升java程序的运行速度,我们经常将精力放在代码优化上面,很少关注影响较大的I/O流方面.多数情况下,并非操作系统不能做到快速的传输,而是受到JVM在IO方面限制,导致了操作系统与Java的IO流模型不匹配。操作系统通常在直接存储器(DMA)协助下完成大块数据(缓冲区)移动,但JVM中IO流操作的小块数据(单个字节,几行文本).结果是操作系统送来整个缓冲区,而java中IO只是一小块的读取,但是每一块的读取都要往返几层对象,使得效率很低.NIO就可以做到将整块数据直接放到缓冲区Buffer中.

NIO是在JDK1.4版本的java.nio.*引入的内容,满足了java程序高密度的IO传输,与传统的IO流相比较,读取和写入速度有极大的提升.两者的区别有如下几点:

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

1. IO流是面向流的。

      一次性从流中读取一个字节或者多个字节,直到读取所有的字节.没有直接提供缓冲区,所以没有办法移动流的中数据,除非将数据缓冲区起来(比如RandomAcessFile就可以实现)。而NIO则是面向块的(Buffer缓冲),不管是读取还是写入,都是将数据先放到缓冲区,由于缓冲区本身就是一个数组,所以可以通过操作索引灵活地读取数据.

2. IO流是阻塞的。

      调用read()读取数据或者write()写入数据时,在读取到数据或者完全写入数据之前,线程一直处于阻塞状态,不能干别的事情,java NIO是非阻塞模式的,一个线程从某通道发送请求读取数据,它仅能得到目前能够获取的数据,如果没有数据的时候,不会获取任何的数据,而不是在阻塞状态,所以直至数据变的可以读取之前,可以继续干别的事情.非阻塞写也是如此,一个线程请求通道写入数据,在数据完全写入之前,线程可以去做别的事情.线程将非阻塞的时间用于处理其他通道上执行IO执行,所以一个线程可以管理多个通道.

3. java NIO中的选择器允许一个线程监视多个通道,

      可以将创建的一个或者多个可选择的通道注册到选择器对象中.在通道准备就绪之前,线程可以休眠或者轮询选择器,查看上次检查之后,是否有通道处于就绪状态.需要注意的是一个通道可以注册到多个选择器上面,但是在每个选择器上只能注册一次.

java NIO最重要的三个核心API:缓冲区(Buffer),通道(Channel),选择器(Selector),这也是NIO的创新点.引入"Java编程思想"中比较好的例子,从煤矿里面运出矿藏,通道就是包含的煤层的矿藏,而缓冲区就是装满矿藏的卡车,卡车载满矿藏(数据)而回.我们从而得到了对应的矿藏,从整个过程来看,我们并没有直接和通道进行接触,只是和缓冲区进行了交互,将缓冲区派送到通道,而通道将对应数据放到缓冲区里面,或者从缓冲区获取数据.而通道可能并不仅仅只存在一个,我们将创建的通道注册到选择器上面,通过单个线程就可以管理多个通道.

缓冲区(Buffer)

Buffer对象是固定数量的数据容器,原始是一个字节数组,所以存储到缓冲区的数据可以用于检索. java.nio包下面提供了java基本数据类型除boolean类型以外的对应缓冲区,从通道获取的原始数据是字节数据放到字节缓冲区(ByteBuffer),非字节缓冲区可以执行从字节或者到字节转换.缓冲区的工作与通道紧密联系,通道时IO传输发生时通过的入口,而缓冲区是这些数据传输的来源或者目标.

Buffer作为顶层的抽象类,提供了操作缓冲区的基本方法,子类有ByteBuffer,ShortBuffer,IntBuffer,LongBuffer,CharBuffer,

FloatBuffer,DoubleBuffer对应的java基本数据类型是byte,short,int,long,char,float,double.其中不包含boolean类型(布尔类型无法对应到一个或者多个字节),此外还有MappedByteBuffer,HeapByteBuffer,DirectByteBuffer。

基本数据类型  缓冲区
byte ByteBuffer
short ShortBuffer
int IntBuffer
long LongBuffer
boolean -
char  CharBuffer
float FloatBuffer
double DoubleBuffer
- MappedByteBuffer
- HeapByteBuffer(底层实现,无法直接访问)
- DirectByteBuffer(底层实现,无法直接访问)

通道(Channel)

通道主要是实现了字节缓冲区和通道另一侧的实体(文件或者套接字)之间有效的传输。也就是使用java NIO读写网络数据或者文件,经通道,放到缓冲区。channel类主要在java.nio.channels包中,部分channels将会依赖到java.nio.channels.spi子包中定义的类,标准的IO流是单向的,比如FileInputStream只能用于读取数据,而FileOutputStream只能用于写入数据.通道也可以是单向,也可以双向的.一个channel类实现了定义read()方法的ReadableByteChannel接口,另外一个channel类实现了定义write()方法的WritableByteChannel接口,单一实现了ReadableByteChannel接口或者WritableByteChannel接口都只是单向的,但一个类实现了ReadableByteChannel接口,又实现了WriteableByteChannel接口,就是双向的(既可以向通道中写入数据,也可以读取数据).

通道分为两种类型:文件(file)通道套接字(socket)通道,文件通道有FileChannel.而套接字(socket)通道包含了SocketChannel,ServerSocketChannel,DatagramChannel,每个Socket通道都关联一个java.net.socket对象.通道有多种创建方式,其中socket通道有直接创建的工厂方法,但是FileChannel只能在打开的FileInputStream,FileOutputStream以及RandomAccessFile对象上调用getChannel()获得,没有直接创建FileChannel对象的方法.关于通道的说明:

    a.文件通道(FileChannel):

  • FileChannel---从文件中读取数据或者写入数据到文件中

    b.套接字通道(SocketChannel,DatagramChannel,ServerSocketChannel):

  • SocketChannel---模拟连接导向的流协议(TCP/IP),点对点,有序的网络连接,连接成功,只能从连接的地址中接收数据或者发送数据到连接的指定地址。
  • DatagramChannel---模拟无连接导向的流协议(UDP/IP),可以接收任何目的的数据,也可以发送数据给不同目的地址。
  • ServerSocketChannel---本身不传输数据,负责监听传入的连接和创建新的SocketChannel对象.

选择器(Selector)

一个通道或者多个通道可以注册到选择器对象,选择器对象管理就是这些被注册通道集合以及他们的就绪状态.当一个通道注册到选择器上面就会有一个对应的选择键SelectionKey对象返回,SelectionKey对象中包含选择器和对应注册的通道.调用选择键中对应的方法可以关闭注册到选择器上的通道或者通过关闭选择器使选择键失效.

在检测到注册到选择器上的通道就绪之前,线程可以休眠,或者通过周期性的轮询选择器,查看上次检查后是否有通道处于就绪状态,所以单个线程就可以做到管理多个通道,即管理多个连接。

                                                 

简单案例

1.传统IO流与文件通道FileChannel比较

public class FileChannelDemo {
  public static void main(String[] args) throws IOException {
    testStandardIO();
    testFileChannel();
  }
  
  private static void testFileChannel() throws IOException {
    FileInputStream fis = new FileInputStream(new File("D:\\java.txt"));
    FileChannel channel = fis.getChannel();
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    channel.read(buffer);
    buffer.flip();
    System.out.println(buffer.toString());
    channel.close();
    fis.close();
  }
  private static void testStandardIO() throws FileNotFoundException, IOException {
    FileInputStream fis = new FileInputStream(new File("D:\\java.txt"));
    byte[] buffer =new byte[1024];
    int len;
    while((len=fis.read(buffer))!=-1) {
      System.out.println(new String(buffer));
    }
    fis.close();
  }
}

2.SocketChannel和ServerSocketChannel的配合使用.

//发送端
public class SocketChannelDemo {
  public static void main(String[] args) throws IOException {
    sendData();
  }

  private static void sendData() throws IOException {
    SocketChannel socketChannel = SocketChannel.open();
    socketChannel.configureBlocking(false);
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    socketChannel.connect(new InetSocketAddress(1234));
    if(socketChannel.finishConnect()) {
      int i = 0;
      while(true) {
        String info = "this is info from SocketChannel"+i++;
        buffer.clear();
        buffer.put(info.getBytes());
        buffer.flip();
        while(buffer.hasRemaining()) {
          socketChannel.write(buffer);
        }
      }
    }
    socketChannel.close();
  }
}
//接收端
public class ServerSocketChannelDemo {
  public static void main(String[] args) throws IOException, InterruptedException {
    receiveData();
  }

  private static void receiveData() throws IOException, InterruptedException {
    ServerSocketChannel ssc = ServerSocketChannel.open();
    ssc.bind(new InetSocketAddress(1234));
    ssc.configureBlocking(false);
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    String decoding = System.getProperty("file.encoding");
    while(true) {
      SocketChannel socketChannel = ssc.accept();
      if(socketChannel!=null) {
        InetSocketAddress removeAddress=(InetSocketAddress)socketChannel.getRemoteAddress();
        System.out.println(removeAddress.getPort()+"---"+removeAddress.getAddress());
        socketChannel.read(buffer);
        buffer.flip();
        while(buffer.hasRemaining()) {
          System.out.println(Charset.forName(decoding).decode(buffer));
        }
      }else{
        Thread.sleep(1000);
      }
    }
  }
}

3.DatagramChannel

//发送端
public class DatagramSendDemo {
  public static void main(String[] args) throws IOException {
    sendData();
  }

  private static void sendData() throws IOException {
    DatagramChannel channel = DatagramChannel.open();
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    buffer.clear();
    buffer.put("helloworld".getBytes());
    buffer.flip();
    channel.send(buffer,new InetSocketAddress("127.0.0.1",1234));
  }
}
//接收端
public class DatagramReceiveDemo {
  public static void main(String[] args) throws IOException {
    receiveData();
  }

  private static void receiveData() throws IOException {
    DatagramChannel channel = DatagramChannel.open();
    channel.bind(new InetSocketAddress("127.0.0.1",1234));
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    while(true) {
      buffer.clear();
      channel.receive(buffer);
      buffer.flip();
      String encoding = System.getProperty("file.encoding");
      while(buffer.hasRemaining()) {
        System.out.println(Charset.forName(encoding).decode(buffer));
      }
    }
  }
}

总结

传统的IO流主要是单向传输(如FileInputStream只能读取数据,FileOutputStream只能写入数据),读取/写入单个字节或者文本行,直到处理完所有的数据.Java NIO是全新的内容,一个类同时实现了ReadableByteChannel接口和WritableByteChannel接口,就可以做到双向传输(一个类中即有读取方法,也有写入方法,类似于IO流中RandomAccessFile),通过通道(Channel)将数据放到缓冲区(Buffer)中,实现了读取/写入大块数据。相比较传统IO流,做到高效率传输。

猜你喜欢

转载自blog.csdn.net/lili13897741554/article/details/82691723