JAVA NIO

NIO是非阻塞的IO,Java NIO由一下几个核心部分组成:BuffersChannelsSelectors

Java NIO中有很多类和组件,但是BuffersChannelsSelectors构成了核心的API。其他组件如PipeFileLock,只不过是与其他三个核心组件共同使用的工具类。

  • Channel:基本上所有的IO在NIO中都从一个Channel开始,Channel有点像流。数据可以从Channel读到Buffer中,也可以从Buffer写到Channel中。Channel和Buffer有好多类型,Channel主要有:FileChannel、DataGramChannel、SocketChannel、ServerSocketChannel。涵盖了UDP和TCP网络的IO以及文件IO。
  • Buffer:NIO主要的Buffer有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,这些Buffer涵盖了你能通过IO发送的基本数据类型。
  • Selector:允许单线程处理多个Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。例如一个聊天服务器中。要使用Selector,得先向Selector注册Channel然后调用它的select()方法。这个方法会一直堵塞知道某个注册的通道有事件就绪。一旦这个方法返回线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。

缓冲区&Buffer

在整个Java新IO中,所有的操作都是以缓冲区进行的,使用缓冲区,则操作的性能将是最高的。

在Buffer中存在一系列的状态变量,随着写入或读取都有可能被改变,在缓冲区可以使用三个值表示缓冲区状态

  • position 位置
  • limit 界限
  • capacity 容量

ByteBuffer的使用

  1. 创建ByteBuffer

(1)使用allocate()静态方法ByteBuffer buffer=ByteBuffer.allocate(256);
以上方法将创建一个容量为256字节的ByteBuffer,如果发现创建的缓冲区容量太小,唯一的选择就是重新创建一个大小合适的缓冲区.

(2)通过包装一个已有的数组来创建
如下,通过包装的方法创建的缓冲区保留了被包装数组内保存的数据.
ByteBuffer buffer=ByteBuffer.wrap(byteArray);
如果要将一个字符串存入ByteBuffer,可以如下操作:
String sendString="你好,服务器. ";
ByteBuffer sendBuffer=ByteBuffer.wrap(sendString.getBytes("UTF-16"));

  1. 缓冲区
  • buffer.flip();

这个方法用来将缓冲区准备为数据传出状态,执行以上方法后,输出通道会从数据的开头而不是末尾开始.回绕保持缓冲区中的数据不变,只是准备写入而不是读取.

  • buffer.clear();

这个方法实际上也不会改变缓冲区的数据,而只是简单的重置了缓冲区的主要索引值.不必为了每次读写都创建新的缓冲区,那样做会降低性能.相反,要重用现在的缓冲区,在再次读取之前要清除缓冲区.

ByteBuffer转为其他的Buffer,如:CharBuffer、DoubleBuffer、IntBuffer、LongBuffer、ShortBuffer,都有对应的asXXXBuffer()方法。

  • 创建子缓冲区 buf.slice()
    子缓冲区可以修改数据

import java.nio.IntBuffer ;
public class IntBufferDemo02{
public static void main(String args[]){
IntBuffer buf = IntBuffer.allocate(10) ; // 准备出10个大小的缓冲区
IntBuffer sub = null ; // 定义子缓冲区
for(int i=0;i<10;i++){
buf.put(2 * i + 1) ; // 在主缓冲区中加入10个奇数
}

    // 需要通过slice() 创建子缓冲区
    buf.position(2) ;
    buf.limit(6) ;
    sub = buf.slice() ;
    for(int i=0;i

}


- 创建只读缓冲区 buf.asReadOnlyBuffer()
```java
import java.nio.IntBuffer ;
public class IntBufferDemo03{
  public static void main(String args[]){
      IntBuffer buf = IntBuffer.allocate(10) ;    // 准备出10个大小的缓冲区
      IntBuffer read = null ; // 定义子缓冲区
      for(int i=0;i<10;i++){
          buf.put(2 * i + 1) ;    // 在主缓冲区中加入10个奇数
      }
      read = buf.asReadOnlyBuffer()  ;// 创建只读缓冲区
      
      read.flip() ;   // 重设缓冲区
      System.out.print("主缓冲区中的内容:") ;
      while(read.hasRemaining()){
          int x = read.get() ;
          System.out.print(x + "、") ;
      }
      read.put(30) ;  // 修改,错误
  }
}
  • 创建直接缓冲区 public static ByteBuffer allocateDirect(int capacity)
    只能提高一些尽可能的性能。
import java.nio.ByteBuffer ;
public class ByteBufferDemo01{
    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 + "、") ;
        }
    }
}

通道

  • 可读、可写。程序不会直接操作通道,所有的内容读到或者写入缓冲区,再通过缓冲区中取得或写入。
  • 传统的流操作分为输入或输出流,而通道本身是双向操作的,可以完成输入也可以完成输出。

读入方式

  • RandomAccessFile:较慢
  • FileInputStream:较慢
  • 缓存读取:速度较快
  • 内存映射MapByteBuffer:最快!

FileChannel的三种内存映射模式

|NO. | 常量|类型|描述|
| ------------- |------------|------------|-------------|---|
|1|public static final FileChannel.MapMode.READ_ONLY|常量|只读映射模式|
|2|public static final FileChannel.MapMode.READ_WRITE|常量|读取/写入映射模式|
|3|public static final FileChannel.MapMode.PRIVATE |常量|专用(写入时拷贝)映射模式|

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 + "mldn.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() ;
    }
}

内存映射在读取时速度快,但是如果在使用以上操作代码的时候,执行的是写入操作则有可能非常危险,因为仅仅是改变数组中单个元素这种简单的操作,就可能直接修改磁盘上的文件,因为修改数据与将数据保存在磁盘上是一样的。


文件锁FileLock

当一个线程将文件锁定之后,其它线程无法操作此文件,要想进行文件锁定操作,需要使用FileLock类完成,此类的对象需要依靠FileChannel进行实例化操作。

JAVA NIO_第1张图片
实例化FileLock对象的方法

锁定方式

  • 共享锁:允许多个线程进行文件的读/写操作

  • 独占锁:只允许一个线程进行文件的读/写操作

    JAVA NIO_第2张图片

字符集 Charset

整个NIO中,对于不同平台的编码操作,java都可以进行自动适应,因为可以使用字符集进行字符编码的转换。

在java中,所有信息都是以UNICODE进行编码,计算机中存在多种编码,NIO中提供了charset类来处理编码问题,包括了创建编码器(CharsetEndoder)和创建解码器(CharsetDecoder)的操作。

JAVA NIO_第3张图片
得到所有字符集
        Charset latin1 = Charset.forName("ISO-8859-1") ;    // 只能表示的英文字符
        CharsetEncoder encoder = latin1.newEncoder() ;  // 得到编码器
        CharsetDecoder decoder = latin1.newDecoder() ;  // 得到解码器
        // 通过CharBuffer类中的
        CharBuffer cb = CharBuffer.wrap("啊哈哈哈哈") ;
        ByteBuffer buf = encoder.encode(cb) ;   // 进行编码操作
        System.out.println(decoder.decode(buf)) ;   // 进行解码操作

Selector

解决服务器端的通讯性能。

  • 使用Selector可以构建一个非阻塞的网络服务
  • 在NIO中实现网络程序需要依靠ServerSocketChannel类与SocketChannel
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 selectedKeys = selector.selectedKeys();// 取出全部生成的key
            Iterator 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
        }

    }
}

你可能感兴趣的:(JAVA NIO)