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<sub.capacity();i++){
     int temp = sub.get(i) ;
     sub.put(temp-1) ;
   }

   buf.flip() ;  // 重设缓冲区
   buf.limit(buf.capacity()) ;
   System.out.print("主缓冲区中的内容:") ;
   while(buf.hasRemaining()){
     int x = buf.get() ;
     System.out.print(x + "、") ;
   }
 }
}
  • 创建只读缓冲区 buf.asReadOnlyBuffer()
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进行实例化操作。

实例化`FileLock`对象的方法

锁定方式

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

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


字符集 Charset

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

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

得到所有字符集
        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<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
    }

  }
}

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容