Java中的NIO

引言:NIO是Java面试中老生常谈的一个话题,No-Block-IO(非阻塞IO);今天专花了一天时间将并发变成网站上便于NIO的东西全看了一遍,下面算是自己的一个学习笔记,方便以后回顾:

一:NIO简介

  • 1:基本概念nio 是non-blocking的简称,在jdk1.4 里提供的新api 。Sun 官方标榜的特性如下: 为所有的原始类型提供(Buffer)缓存支持。字符集编码解码解决方案。
    Channel :一个新的原始I/O 抽象。 支持锁和内存映射文件的文件访问接口。 提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O 。

  • 2:几个核心类

    • A:channel
    • B:buffer
    • C:selector
      2.1:channel:NIO中的通道,类比于JavaIO就相当于JavaIO中的流;基本上,所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。
      2.2:buffer:Buffer 类是 java.nio 的构造基础。一个 Buffer 对象是固定数量的数据的容器,其作用是一个存储器,或者分段运输区,在这里,数据可被存储并在之后用于检索。缓冲区可以被写满或释放。对于每个非布尔原始数据类型都有一个缓冲区类,即 Buffer 的子类有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer 和 ShortBuffer,是没有 BooleanBuffer 之说的。Java NIO 还有个 MappedByteBuffer,用于表示内存映射文件
    • 缓冲区的四个属性:
    • 1:容量(Capacity):缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变。
    • 2:上界(Limit):缓冲区的第一个不能被读或写的元素。缓冲创建时,limit 的值等于 capacity 的值。假设 capacity = 1024,我们在程序中设置了 limit = 512,说明,Buffer 的容量为 1024,但是从 512 之后既不能读也不能写,因此可以理解成,Buffer 的实际可用大小为 512。
    • 3:”位置(Position):下一个要被读或写的元素的索引。位置会自动由相应的 get() 和 put() 函数更新。
    • 4:标记(Mark):一个备忘位置

    2.3:selector:允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。


    overview-selectors.png

二:具体用法:

  • 2.1:Buffer的基本用法
    使用Buffer读写数据一般遵循以下四个步骤:
  • 1:写入数据到Buffer
  • 2:调用flip()方法
  • 3:从Buffer中读取数据
  • 4:调用clear()方法或者compact()方法
    当向buffer写入数据时,buffer会记录下写了多少数据。一旦要读取数据,需要通过flip()方法将Buffer从写模式切换到读模式。在读模式下,可以读取之前写入到buffer的所有数据。
    一个小Demo:
public class deme1Nio {
    public static void main(String[] args) {
        File file = new File("F:\\1.txt");
        try {
            RandomAccessFile randomfile  = new RandomAccessFile(file, "rw");
            FileChannel channel = randomfile.getChannel();
            ByteBuffer buff = ByteBuffer.allocate(48);
            int data = channel.read(buff);
            while(data!=-1){
                //去读到文件的最后位置,安字节算的;换行:算两个字节
                int pos = buff.position();
                System.out.println(",pos="+pos);
                System.out.println(data);
                //切换到读模式
                buff.flip();
                while(buff.hasRemaining()){ 
                    byte[] bytes = new byte[pos];
                    buff.get(bytes);
                    System.out.print(new String(bytes,"GBK"));
                }
                buff.clear();
                data = channel.read(buff);
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
  • 2.2:聚合和分散:
  • 聚合:写入Channel是指在写操作时将多个buffer的数据写入同一个Channel,因此,Channel 将多个Buffer中的数据“聚集(gather)”后发送到Channel。
  • 分撒:从Channel中读取是指在读操作时将读取的数据写入多个buffer中。因此,Channel将从Channel中读取的数据“分散(scatter)”到多个Buffer中。
  • 应用:读取报文头信息和报文体信息时;
File file1 = new File("f:\\1.txt"); 
        FileInputStream in1;
        try {
            in1 = new FileInputStream(file1);
            FileChannel channel1 = in1.getChannel();
             //用来存储头部信息
            ByteBuffer head = ByteBuffer.allocate(5);
             //用来存储区body信息
            ByteBuffer body = ByteBuffer.allocate(10);
            //将两个信息整合在一起
            ByteBuffer[] arr = {head,body};
            long data1 = channel1.read(arr);
            while(data1!=-1){
                head.flip();
                while(head.hasRemaining()){ 
                    byte[] he = new byte[head.limit()];
                    head.get(he);
                    System.out.println("头部信息为"+new String(he,"gbk"));
                }
                body.flip();
                while(body.hasRemaining()){
                    byte[] bo = new byte[body.limit()];
                    body.get(bo);
                    System.out.println("内容体信息为"+new String(bo,"gbk"));
                }
                head.compact();
                body.compact();
                data1 = channel1.read(arr);
            }
            
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
    }
    public static void writeCombine(){
        File file1 = new File("f:\\2.txt"); 
        RandomAccessFile in1;
        try {
            in1 = new RandomAccessFile(file1,"rw");
             
            FileChannel channel1 = in1.getChannel();
             //用来存储头部信息
            ByteBuffer head = ByteBuffer.allocate(5);
            head.put("99999".getBytes());
             //用来存储区body信息
            ByteBuffer body = ByteBuffer.allocate(10);
            body.put("1111111111".getBytes());
            //将两个信息整合在一起
            ByteBuffer[] arr = {head,body};
            head.flip();
            body.flip();
            channel1.write(arr);
            channel1.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
  • 2.3:通道之间传输数据:
  • transfromFrom()和transfromTo()方法的使用;
  • FileChannel的transferFrom()方法可以将数据从源通道传输到FileChannel中;
  • transferTo()方法将数据从FileChannel传输到其他的channel中
public class demo3Transform {
    public static void trasfromF(){
        File  file1 = new File("F:\\1.txt");
        File  file2 = new File("F:\\2.txt");
        try {
            RandomAccessFile rfile1 = new RandomAccessFile(file1, "rw");
            RandomAccessFile rfile2 = new RandomAccessFile(file2, "rw");
            FileChannel channel1 = rfile1.getChannel();
            FileChannel channel2 = rfile2.getChannel();
            long position = channel2.position();
            long size = channel2.size();
            //将channel2的内容复制到channel1中去;(从1的position的位置开始)
            channel1.transferFrom(channel2, 2, size);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    public static void trasfromT(){
        File  file1 = new File("F:\\1.txt");
        File  file2 = new File("F:\\2.txt");
        try {
            RandomAccessFile rfile1 = new RandomAccessFile(file1, "rw");
            RandomAccessFile rfile2 = new RandomAccessFile(file2, "rw");
            FileChannel channel1 = rfile1.getChannel();
            FileChannel channel2 = rfile2.getChannel();
            long position = channel2.position();
            long size = channel2.size();
            //将channel1的内容复制到channel2中去;(从2的position的位置开始,赋值2那么长)
            channel1.transferTo(position, size, channel2);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
      public static void main(String[] args) {
          demo3Transform.trasfromT();
      }
}
  • 2.4:selector的使用;
    仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。
    但是,需要记住,现代的操作系统和CPU在多任务方面表现的越来越好,所以多线程的开销随着时间的推移,变得越来越小了。实际上,如果一个CPU有多个内核,不使用多任务可能是在浪费CPU能力。不管怎么说,关于那种设计的讨论应该放在另一篇不同的文章中。在这里,只要知道使用Selector能够处理多个通道就足够了。
  • 1:Selector的创建
    通过调用Selector.open()方法创建一个Selector,如下:
    elector selector = Selector.open();
  • 2:向Selector注册通道
    为了将Channel和Selector配合使用,必须将channel注册到selector上。通过SelectableChannel.register()方法来实现,如下:
    • 1:channel.configureBlocking(false);
    • 2:SelectionKey key = channel.register(selector,Selectionkey.OP_READ);
      与Selector一起使用时,Channel必须处于非阻塞模式下。这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以。
  • 3:SelectionKey
    在上一小节中,当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了一些属性:
    • interest集合
    • ready集合
    • Channel
    • Selector
    • 附加的对象(可选)
      下面我会描述这些属性。
    • interest集合
      可以通过SelectionKey读写interest集合
    • ready集合
      ready 集合是通道已经准备就绪的操作的集合。在一次选择(Selection)之后,你会首先访问这个ready的set集合。
    • 附加的对象
      可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。
    • 通过Selector选择通道
      一旦向Selector注册了一或多个通道,就可以调用几个重载的select()方法。这些方法返回你所感兴趣的事件(如连接、接受、读或写)已经准备就绪的那些通道。换句话说,如果你对“读就绪”的通道感兴趣,select()方法会返回读事件已经就绪的那些通道。
      下面是select()方法:
      • int select()
      • int select(long timeout)
      • int selectNow()
      • select()阻塞到至少有一个通道在你注册的事件上就绪了。
      • select(long timeout)和select()一样,除了最长会阻塞timeout毫秒(参数)。
      • selectNow()不会阻塞,不管什么通道就绪都立刻返回
        select()方法返回的int值表示有多少通道已经就绪
    • selectedKeys()
      一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道
Set selectedKeys = selector.selectedKeys();
Iterator keyIterator = selectedKeys.iterator(); 
while(keyIterator.hasNext()) {    
    SelectionKey key = keyIterator.next();
if(key.isAcceptable())       
  // a connection was accepted by a ServerSocketChannel. 
} else if (key.isConnectable()) {
// a connection was established with a remote server.   
}else if (key.isReadable()) {
// a channel is ready for reading
} else if(key.isWritable()) {   
// a channel is ready for writing
}  
keyIterator.remove();
}

四:IO和NIO

1:区别:


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

推荐阅读更多精彩内容