NIO编程,New IO?

在学习NIO之前,我们需要先了解几个概念

  • 什么是同步、异步IO?

同步IO是指某一线程在发出一个调用时,在调用结果没有结束之前,一直等待,调用不返回。

异步IO是指当某一线程发出一个调用时,因为不能立刻得到结果,所以该线程可以去做其它的事情,等原来的调用有了结果,以状态、通知或者回调的方式通知调用者。

  • 什么是阻塞、非阻塞?

阻塞是指该线程无法做其它任务,只有条件就绪时才能继续

非阻塞是指不管IO操作是否结束,直接返回,相应操作在后台继续处理。

有的同学会混淆同步和阻塞、异步和非阻塞的概念,认为它们是等同的,其实它们还是有区别的,可以这样理解:

同步/异步的区别在在于进程是否阻塞,可以理解为实际为进程操作

阻塞和非阻塞的区别在于应用程序的调用是否立即返回,可以理解为IO请求

有了这些概念,我们学习下NIO与IO有哪些不同。

1、NIO与IO

翻译了jenkov的文档,总结NIO和IO如下表

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

通过表可以看出,NIO的在原有IO的做了很大的改变,我们具体分析下

  • 面向流VS面向缓冲

java IO 面向流意味着可以从流中读取一个或多个字节,这些字节不会存放在任何在任何缓存中,流中数据的流向是单向的,如果需要移动来回移动流中的数据,需要将这些数据存存放在缓冲区中。

Java NIO的面向缓冲是将数据读入到缓冲区,从缓冲区中拿数据,存放在缓冲区中的数据可以来回移动,从缓冲区拿数据时,需要先检查缓冲区是否存在自己想要的数据,当向缓冲区写数据时,要确保不要覆盖缓冲区中未处理的数据。
在这里插入图片描述

  • 阻塞IO VS 非阻塞IO

Java IO当线程调用read()和write()时,该线程将会被阻塞,直到有一些数据读取结束或者被完全写入为止,在此期间,线程将无法执行其它任何操作。

Java NIO非阻塞模式可以从通道读取数据,并且可以获取当前可用的内容,如果当前没有可用的数据,线程可以继续处理其它的事情,而不是在数据可供读取之前保持阻塞状态。

  • 选择器

Java NIO选择器允许单个线程监视多个输入通道,可以使用选择器注册多个通道,然后使用单个线程选择具有可用于处理输入的通道,或者选择已经准准备好进入写入的通道,这种选择器机制可以使用单个线程管理多个通道。

**NOTE :**IO和NIO都是同步的,因为NIO中的accept/read/write方法的内核操作都会阻塞当前线程。

2、NIO的功能

NIO主要有三个组成部分,分别为Channel(通道)、Buffer(缓冲区)、Selector(选择器)

2.1通道

通道表示打开到IO设备(如文件)的连接,类似于传统IO中的流的传输,在NIO中,Channel本身不能直接访问数据,Channel只能与Buffer进行交互,Java为Channel接口提供了四个主要的实现类

继承链为:
在这里插入图片描述

FileChannel读取、写入、映射和操作文件的通道
DatagramChannel通过UDP读写网络中的数据通道
SocketChannel通过TCP读写网络中的数据
ServerSocketChannel可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个ServerSocketChannel

本文主要讲解FileChannel,其余三个放在网络编程的专题中讲解。

获取通道需要使用支持通道的对象调用getChannel()方法。

支持通道的类有

  • FileInputStream、FileOutputStream、RandomAccessFile、DatagramSocket、Socket、ServerSocket
2.2缓冲区

缓冲区(Buffer)是一个包含特定类型的容器,在java NIO中,Buffer用于和通道进行交互,数据从通道读入缓冲区从缓冲区写入通道。Buffer类似于Java IO中定义的缓存数组,它可以保存多个相同类型的数据,

继承链为:
在这里插入图片描述
继承链中的Buffer的子类仅仅是管理的数据类型不同,它们对数据处理的方法是相似的。

Buffer的子类中提供了两个用于数据操作的方法:get()和put()

  • 获取buffer中的数据

get():读取单个字节

get(byte[] dst):读取多个字节到dst中

get(int index):读取指定位置的字节

//get():读取单个字节
public abstract byte get();
//get(byte[] dst):读取多个字节到dst中
public ByteBuffer get(byte[] dst) {
        return get(dst, 0, dst.length);
    }
//get(int index):读取指定位置的字节
public abstract byte get(int index);
  • 将数据放入到buffer中

put(byte b):将给定单个字节写入缓冲区的当前位置

put(byte() src):将 src 中的字节写入缓冲区的当前位置

put(int index,byte b):将指定字节写入缓冲区的索引位置

//put(byte b):将给定单个字节写入缓冲区的当前位置
public abstract ByteBuffer put(byte b);
//put(byte() src):将 src 中的字节写入缓冲区的当前位置
 public ByteBuffer put(ByteBuffer src) {
        if (src == this)
            throw new IllegalArgumentException();
        if (isReadOnly())
            throw new ReadOnlyBufferException();
        int n = src.remaining();
        if (n > remaining())
            throw new BufferOverflowException();
        for (int i = 0; i < n; i++)
            put(src.get());
        return this;
    }
//put(int index,byte b):将指定字节写入缓冲区的索引位置
   public abstract ByteBuffer put(int index, byte b);

打开Buffer类,可以发现主要有以下几个重要的属性

capacity:表示buffer的最大数据容量,不能为负,创建后不能修改。

limit:不能被写入或读取的第一个位置的索引,limit后的数据不能被读写

position:下一个要读取或写入数据的索引

mark:标记一个索引,通过buffer中的mark()方法指定Buffer中一个特定的position,之后可以通过reset方法恢复到该位置。

    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;

在这里插入图片描述

缓冲区常用的方法,参考jdk8开发手册

方法描述
Buffer clean()清空缓存并返回对缓冲区的引用
Buffer flip()将缓冲区的界限设置为当前位置,并将当前位置设置为0
int capacity()返回Buffer的capacity大小
boolean hasRemaining()判断缓冲区是否还有元素
int limit()返回 Buffer 的界限(limit) 的位置
Buffer limit(int n)将设置缓冲区界限为 n, 并返回一个具有新 limit 的缓冲区对象
Buffer mark()对缓冲区设置标记
int position()返回缓冲区的当前位置 position
Buffer position(int n)将设置缓冲区的当前位置为 n , 并返回修改后的 Buffer 对象
int remaining()返回 position 和 limit 之间的元素个数
Buffer reset()将位置 position 转到以前设置的 mark 所在的位置
Buffer rewind()将位置设为 0, 取消设置的 mark
//Buffer clear()方法,清空缓存并返回对缓冲区的引用
public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }
//Buffer flip()方法,将缓冲区的界限设置为当前位置,并将当前位置设置为0
    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
//Buffer rewind() 将位置设为 0, 取消设置的 mark
public final Buffer rewind() {
        position = 0;
        mark = -1;
        return this;
    }
//remaining()返回 position 和 limit 之间的元素个数
public final int remaining() {
        return limit - position;
    }
//hasRemaining,判断缓冲区是否还有元素
public final boolean hasRemaining() {
        return position < limit;
    }
//limit(),返回 Buffer 的界限(limit) 的位置
public final int limit() {
        return limit;
    }
//Buffer position(),将设置缓冲区的当前位置为 n , 并返回修改后的 Buffer 对象
public final Buffer position(int newPosition) {
        if ((newPosition > limit) || (newPosition < 0))
            throw new IllegalArgumentException();
        position = newPosition;
        if (mark > position) mark = -1;
        return this;
    }
//Buffer limit(),将设置缓冲区界限为 n, 并返回一个具有新 limit 的缓冲区对象
public final Buffer limit(int newLimit) {
        if ((newLimit > capacity) || (newLimit < 0))
            throw new IllegalArgumentException();
        limit = newLimit;
        if (position > limit) position = limit;
        if (mark > limit) mark = -1;
        return this;
    }
//Buffer mark(),对缓冲区设置标记
public final Buffer mark() {
        mark = position;
        return this;
    }
//Buffer reset(),将位置 position 转到以前设置的 mark 所在的位置
public final Buffer reset() {
        int m = mark;
        if (m < 0)
            throw new InvalidMarkException();
        position = m;
        return this;
    }

缓冲区还支持分散读取和聚集写入

  • 分散读取

将channel中的分散到多个buffer中,按照缓冲区的顺序,依次将Buffer填满

  • 聚集写入

将多个buffer中的数据聚集到Channel,按照缓冲区的顺序,写入channel

2.3选择器

选择器部分将放在网络编程专题中讲解。

3、NIO的使用

3.1Channel的使用
  • 利用通道完成文件的复制

    核心代码如下

 public void test1() {
        FileInputStream fis = null;
        FileOutputStream fos = null;
        //获取通道
        FileChannel inChannel = null;
        FileChannel outChannel = null;
        try {
            fis = new FileInputStream("F:\\学习笔记\\Simon学习笔记\\java\\wife.jpg");
            fos = new FileOutputStream("F:\\学习笔记\\Simon学习笔记\\java\\wife2.jpg");
            inChannel=fis.getChannel();
            outChannel=fos.getChannel();
            //分配指定大小的缓冲器
            ByteBuffer buf = ByteBuffer.allocate(1024);
            //将通道中的数据存入缓冲区
            while (inChannel.read(buf) != -1) {
                //切换读数据模式
                buf.flip();
                //将缓冲区的数据写入通道
                outChannel.write(buf);
                //清空缓冲区
                buf.clear();
            }
            catch(Exception e){}
            finally(){}
        }
  • 分散读取与聚集写入

    public void test2() throws IOException{
        RandomAccessFile raf1=new RandomAccessFile("F:\\学习笔记\\Simon学习笔记\\java\\test.txt","rw");
        //1、获取通道
        FileChannel channel1=raf1.getChannel();
        //分配指定大小的缓冲区
        ByteBuffer buf1=ByteBuffer.allocate(512);
        ByteBuffer buf2=ByteBuffer.allocate(1024);
        //3、分散读取
        ByteBuffer[] bufs={buf1,buf2};
        channel1.read(bufs);
        for (ByteBuffer byteBuffer:bufs){
            byteBuffer.flip();
        }
        System.out.println(new String(bufs[0].array(), 0, bufs[0].limit()));
        System.out.println("-----------------");
        System.out.println(new String(bufs[1].array(), 0, bufs[1].limit()));
    
        //4. 聚集写入
        RandomAccessFile raf2 = new RandomAccessFile("F:\\学习笔记\\Simon学习笔记\\java\\test2.txt", "rw");
        FileChannel channel2 = raf2.getChannel();
    
        channel2.write(bufs);
        }
    
3.2Buffer的使用

对于Buffer,我们主要测试Buffer中的属性

  • 分配指定大小的缓冲区
 ByteBuffer buf=ByteBuffer.allocate(1024);
  • 使用put()存入数据到缓冲区
String str="今天,你是否博学了!"
ByteBuffer buf=ByteBuffer.allocate(1024);
buf.put(str.getBytes());
  • 切换读取模式
ByteBuffer.flip();
  • 使用get()读取缓存中的数据
byte[] dst = new byte[buf.limit()];
buf.get(dst);
  • 可重复读操作
ByteBuffer.rewind();
  • 清空缓冲区
ByteBuffer.clear();

关注公众号:10分钟编程,获得独家整理的学习资源和日常干货推送
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值