NIO里面的Buffer和Channel

1.NIO和IO的区别

首先传统的IO是面向流的。而NIO是面向缓冲区的。当使用传统IO读取数据的时候,如果数据没有准备好那么线程将会一直阻塞。但是如果使用NIO进行读取时,如果数据没有准备好,那么会直接返回到缓冲区里面已经存在的数据,不会让线程发生阻塞。另外由于NIO是面向缓冲区的,所以通过缓冲区可以读取缓冲区里面任意位置的数据。很多框架比如Netty底层就是使用了NIO的方式进行读取。

2.NIO的两个基础类 Buffer和Channel类

当使用NIO读取文件的时候,你直接操作的对象是Buffer,是先通过Channel把数据读到Buffer里面的。
当使用NIO写入文件,你是通过Channel把Buffer的数据写入到文件里面的。
下面是简答的示意图
在这里插入图片描述

1.Buffer类

Buffer类下面有这些分类
ByteBuffer、MappedByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer
我们可以通过这些数据类型来对Buffer进行操作
Buffer类的一些基本方法

1.分配固定大小的buffer

//分配两个字节大小的字节缓冲区
 ByteBuffer byteBuffer = ByteBuffer.allocate(2);
 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(2);
//两个方法的区别是上面分配的内存是JVM里面堆上面的内存,下面分配的内存是JVM外面的直接内存来使用

allocate和allocateDirect的区别
注释里面写到allocate是分配在堆里面的,读写是还需要先把文件拷贝到内核内存里面才会进行磁盘上的读写。但是allocateDirect方法分配的内存直接就是堆外内存。这部分内存只有在JVM进行FullGc的时候才会对这个区域进行释放。
这部分堆外内存我们可以通过XX:MaxDirectMemorySize参数来控制。使用堆外内存进行读写的效率要高于堆内存几十倍。但是重点就是这部分的垃圾回收问题。如果长时间不触发FullGC,那么这部分内存怎么办。我们也可以通过代码来手动释放内存。

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(2);
((DirectBuffer)byteBuffer).cleaner().clean();
//释放这部分内存  注意只有allocateDirect这种方式进行分配内存才可以调用clean方法手动释放内存
 public static ByteBuffer allocateDirect(int capacity) {
        return new DirectByteBuffer(capacity);
    }
 public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }
//上面是源码  可以看出调用allocateDirect创建的是DirectByteBuffer对象 调用allocate创建的是HeapByteBuffer对象

2.Buffer类的三个重要属性

position、limit、capacity

模式positionlimitcapacity
读的位置,开始是0,逐渐增长能够读的最多的大小,跟当前buffer里面数据的大小相同 ,最大值为capacity-1buffer的容量
开始写的位置,逐渐增长能够写入的最大大小 跟capacity值相同buffer的容量

他们三个都是Buffer类的属性,并且提供了我们修改和读取的方法,注意无法改变capacity的值,只可以读

//修改  返回值是修改后Buffer
Buffer new=byteBuffer.limit(8);
// 读取 返回值是limit的值
int limit = byteBuffer.limit();

3.切换模式

Buffer类既可以用来读也可以用来写。他只要是空间足够就是可以直接写的,但是我们想要读的时候,就需要把上面的三个属性其中的position和limit进行修改。
上面如果理解了你就知道切换到读状态需要把position设置为0,把limit设置为现在Buffer区写模式下的position。

// 写模式转化为读模式
byteBuffer.flip();
//java源代码
public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

4.数据的清理

一个Buffer就那么大,如果你一直写写满了,那么如果数据满了,就没办法继续写了。这就需要我们手动来清理Buffer。

 //clear指的值清除所有的数据
 //compact指的是清除已经被读过的数据
 byteBuffer.clear();
 byteBuffer2.compact();

5.手动初始化缓冲区

byteBuffer.put("sun".getBytes());

2.channel类

channel类似于一种管道的存在。他和Buffer相辅相成。上面的图已经画出来两者的关系了。
这个类也有下面四个分类
FileChannel 文件里面读写数据
DatagramChannel UDP协议读写数据
SocketChannel TCP协议读取数据
ServerSocketChannel 可以监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel。
这个类的方法很简单,我们先按照第一个来举例,后面我们会继续来讲。

//只有这个类型的file才可以使用channel 它和普通file的区别就是可以任意读取某个位置的数据,他是支持缓冲方式读取数据的
randomAccessFile = new RandomAccessFile(fileName, "rw");
//文件里面获取管道
FileChannel fileChannel = randomAccessFile.getChannel();
//手动向缓冲区里面写值 上面忘记写了
byteBuffer.put("sun".getBytes());
//把channel里面的值读到buffer
long b= fileChannel.read(byteBuffer);
//把buffer里面的值通过channel写入到文件
int b= fileChannel.write(byteBuffer);

3.聚集和分散

聚集指的就是把多个buffer的对象写到一个channel里面
分散指的就是把channel的数据读到多个buffer里面

 //分散(scatter)  读的时候读到多个buffer里面
 //聚集 gather  写的时候从多个buffer写入到一个文件
 long a = fileChannel.read(new ByteBuffer[]{byteBuffer,byteBuffer2});
 long b = fileChannel.write(new ByteBuffer[]{byteBuffer,byteBuffer2});

4.多个通道之间的通信

//方法的输入参数position表示从position处开始向目标文件写入数据,count表示最多传输的字节数。如果源通道的剩余空间小于 count 个字节,则所传输的字节数要小于请求的字节数
fromChannel.transferTo(position, count, toChannel);

5.简单的例子

首先有一个txt文件 名字是test.txt 里面的内容是

abcdef博客

下面是读取的方法的代码

 public void readByNio(String fileName) throws IOException {
        RandomAccessFile randomAccessFile = null;
        try {
            
             //通过 RandomAccessFile 获取channel
            randomAccessFile = new RandomAccessFile(fileName, "rw");
            FileChannel fileChannel = randomAccessFile.getChannel();
           
            //通过流获取channel
           /* File file = new File(fileName);
            FileChannel fileChannel = new FileInputStream(file).getChannel();*/

            // 一个channel既可以从多个buffer里面读取,也可以写入到多个buffer
            ByteBuffer byteBuffer = ByteBuffer.allocate(2);
            ByteBuffer byteBuffer2 = ByteBuffer.allocate(2);
           /*   byteBuffer.put("sun".getBytes());
             int b= fileChannel.write(byteBuffer);*/
            //分散(scatter)  读的时候读到多个buffer里面
            //聚集 gather  写的时候从多个buffer写入到一个文件
            long a = fileChannel.read(new ByteBuffer[]{byteBuffer, byteBuffer2});
           /* long b = fileChannel.write(new ByteBuffer[]{byteBuffer, byteBuffer2});*/
            System.out.println(byteBuffer.position()+"=============="+byteBuffer2.position());
            while (a != -1) {
                byteBuffer.flip();
                byteBuffer2.flip();
                //  System.out.println("当前的位置"+byteBuffer.limit());
                //读取字节
                //Charset这个类主要是解码和编码用的,因为我们需要读取的是字符,所以需要对字节进行解码
                String line = Charset.forName("GBK").decode(byteBuffer).toString();
                String line2 = Charset.forName("GBK").decode(byteBuffer2).toString();
                System.out.println(line + "=============" + line2);
                //clear指的值清除所有的数据
                //compact指的是清除已经被读过的数据
                byteBuffer.clear();
                byteBuffer2.clear();
                System.out.println("清理之后"+byteBuffer.position()+"++++++"+byteBuffer2.position());
                a = fileChannel.read(new ByteBuffer[]{byteBuffer, byteBuffer2});
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            randomAccessFile.close();
        }
    }
输出
2==============2
ab=============cd
清理之后0++++++0
ef=============博
清理之后0++++++0=============
清理之后0++++++0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值