Java学习:NIO<一>

一、NIO概述

在JDK4中引入了NIO,可以最大限度的满足Java程序I/O的需求

  • java.nio包,定义了各种与buffer相关的类
  • java.nio.channel包,包含与Channel和Selector相关的类
  • java.nio.charset包,与字符集相关的类

在NIO中有三大核心组件:Channel、Buffer、Selector
传统IO与JavaNIO的区别:

IONIO
传统I/O是面向流的,每次可以从流中读取一个或多个字节,只能向后读取,不能向前移动;NIO是面向缓冲区的,把数据读取到一个缓冲区中,可以在缓冲区中向前/向后移动,增加了程序的灵活性
IO流是线程阻塞的,在调用read()/write()读写数据时,线程阻塞,直到数据读取完毕或者数据完全写入,在读取过程中,线程不能做其它操作;NIO不是线程阻塞的,当线程从Channel中读取数据时,如果通道中没有可用的数据,线程不阻塞,可以做其它的任务
/在NIO中,所有数据都需要通过Channel传输,通道可以直接将一块数据映射到内存中,channel是双向的,不仅可以读取数据,还能保存数据,程序不能直接读写channel通道,Channel只与Buffer缓冲区交互,数据通过channel写到buffer,程序从buffer中读数据

二、Buffer

  • Buffer缓冲区实际上就是一个数组,把数组的内容与信息包装成一个Buffer对象,提供了一组访问这些信息的方法

1、Buffer的属性

属性作用
capacity是指缓冲区可以存储多少个数据,容量在创建buffer缓冲区时指定大小,创建后不能再修改,如果缓冲区满了,需要清空后才能继续写数据
position表示当前位置,即缓冲区写入/读取的位置,刚刚创建Buffer对象后,position初始化为0,写入一个数据,position就向后移动一个单元,它的最大值为capacity - 1,当Buffer从写模式切换到读模式,position会被重置为0,每读一个数据,position就向后移动一个单元
limit是指第一个不能被读取或写入的位置,limit上限后面的单元既不能读也不能写,在Buffer缓冲区写模式下,limit表示能够写入多少个数据;在读取模式下,limit表示最多可以读取多少个数据
mark设置一个标记位置,可以调用mark()方法,把标记设置在position位置,当调用reset()方法时,就把position设置为mark标记的位置

2、Buffer的API

  • 在NIO关键的Buffer有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,这些Buffer覆盖了能够通过I/O发送的所有基本类型:byte、char、double、float、int、long、short等。实际使用较多的是ByteBuffer、CharBuffer
API作用
allocate(capacity)可以创建一个指定容量的缓冲区
put()用于向缓冲区中存储数据
get()用于从缓冲区中读取数据
compact()当缓冲区还有未读完的数据,可以调用compact()方法进行压缩,将所有未读取的数据复制到Buffer的起始位置,把position设置到最后一个未读元素的后面。limit属性设置为capacity
capacity()返回缓冲区大小
limit()返回limit上限的位置
mark()设置缓冲区的标志位置,这个值只能在0 ~ position之间,以后可以通过reset()方法返回到这个位置
position()可以返回position当前位置
remaining()返回当前position位置与limit之间的数据量
rewind()将position设置为0,取消mark标志位
clear()清空缓冲区,仅仅是修改position标志为0,设置limit为capacity,缓冲区数据还是存在的
flip()可以把缓冲区由写模式切换到读模式,先把limit设置为position位置,再把position设置为0
duplicate()缓冲区的复制,之前的缓冲区与实际的缓冲区引用的是同一个数组
slice()分隔缓冲区,根据[position,limit)区间来创建新的缓冲区

3、案例
1)、创建buffer缓冲区

package JavaNIO;

import java.nio.CharBuffer;
import java.util.Arrays;

/*
    缓冲区的创建方式
    1)、分配操作创建缓冲区,allocate()分配一个私有的、指定容量大小的数组来存储元素
    2)、包装操作创建缓冲区,它使用提供的数组作为存储空间来存储缓冲区中的数据,不再分配其他空间
 */
public class CreadBuffer {
    public static void main(String[] args) {
        //1)、分配操作创建缓冲区
        CharBuffer buffer = CharBuffer.allocate(16);

        //2)、包装操作创建缓冲区
        char[] myarray = new char[16];
        //把已存在的数组包装成一个buffer对象
        CharBuffer buffer1 = CharBuffer.wrap(myarray);

        //通过调用put方法向缓冲区中保存数据,也会直接影响到数组
        buffer1.put("hello");
        buffer1.flip();   //切换到读模式
        System.out.println(buffer1);
        System.out.println(Arrays.toString(myarray));

        //对数组做的任何修改,也会影响缓冲区对象

        /*
            不管是allocate()还是wrap()创建的缓冲区都是间接的,间接缓冲区会使用备份数组,
            hasArray()方法可以判断是否有一个可存取的备份数组
            如果hasArray()返回true,可以通过array()返回缓冲区对象使用的备份数组的引用
            备份数组的内容跟原始数组的内容是一致的
         */
        if (buffer1.hasArray()){
            char[] arr2 = buffer1.array();
            System.out.println(Arrays.toString(arr2));
        }

    }
}

2)、相关API应用

package JavaNIO;

import java.nio.CharBuffer;

public class Buffer {
    public static void main(String[] args) {
        //1、创建CharBuffer缓冲区对象
        CharBuffer buffer = CharBuffer.allocate(12);

        //2、打印capacity,limit,position
        System.out.println("capacity:" + buffer.capacity()
        + ",limit:" + buffer.limit() + ",position:" + buffer.position());
        //capacity:12,limit:12,position:0

        //3、向缓冲区存储数据
        buffer.put('成');
        buffer.put('吉');
        buffer.put('思');
        buffer.put('汗');
        System.out.println("capacity:" + buffer.capacity()
                + ",limit:" + buffer.limit() + ",position:" + buffer.position());
        //capacity:12,limit:12,position:4

        //4、调用filp()方法把缓冲区切换为读模式
        buffer.flip();
        System.out.println("capacity:" + buffer.capacity()
                + ",limit:" + buffer.limit() + ",position:" + buffer.position());
        //capacity:12,limit:4,position:0

        //5、调用get()方法直接读取缓冲区数据
        System.out.println(buffer.get());
        System.out.println("capacity:" + buffer.capacity()
                + ",limit:" + buffer.limit() + ",position:" + buffer.position());
        //capacity:12,limit:4,position:1

        //6、再次存储数据,把数据保存在position位置
        buffer.put('X');
        System.out.println("capacity:" + buffer.capacity()
                + ",limit:" + buffer.limit() + ",position:" + buffer.position());
        //capacity:12,limit:4,position:2

        //7、设置标记
        buffer.mark();

        //8、再读取一个字符
        System.out.println(buffer.get());
        System.out.println("capacity:" + buffer.capacity()
                + ",limit:" + buffer.limit() + ",position:" + buffer.position());
        //capacity:12,limit:4,position:3

        //9、调用reset(),把position重置为mark标记位置
        buffer.reset();
        System.out.println("capacity:" + buffer.capacity()
                + ",limit:" + buffer.limit() + ",position:" + buffer.position());
        //capacity:12,limit:4,position:2

        //10、调用compact()压缩,会把postion后的数据移动到起始位置,limit重置为capacity
        buffer.compact();
        System.out.println("capacity:" + buffer.capacity()
                + ",limit:" + buffer.limit() + ",position:" + buffer.position());

        //11、调用clear清空
        buffer.clear();
        System.out.println("capacity:" + buffer.capacity()
                + ",limit:" + buffer.limit() + ",position:" + buffer.position());

        //12、clear清空后,缓冲区的数据依然存在
        while (buffer.hasRemaining()){
            System.out.println(buffer.get());
        }
    }
}

3)、批量读取/写入

package JavaNIO;

import java.nio.CharBuffer;
import java.util.Arrays;

/*
    使用缓冲区就是为了提高数据传输效率,一次读写一个字符或一个字节效率并不高,可以进行批量的操作
    可以借助于数组,把缓冲区中的一块数据读取到数组中,也可以把数组中的部分内容保存到缓冲区中
 */
public class BatchRead {
    public static void main(String[] args) {
        //创建CharBuffer
        CharBuffer buffer = CharBuffer.allocate(15);
        buffer.put("云想衣裳花想容,春风拂槛露华浓");
        //切换为读模式
        buffer.flip();
        System.out.println(buffer);

        //定义字符数组
        char[] dst = new char[8];
        //调用get()方法把缓冲区中的数据读到字符数组中
        //注意批量传输时大小总是固定的,如果没有指定传输的大小,意味着把数组填满
        CharBuffer remainBuffer = buffer.get(dst);
        System.out.println(Arrays.toString(dst));
        //[云, 想, 衣, 裳, 花, 想, 容, ,],相当于把前8位写入字符数组中
        System.out.println(remainBuffer);
        //春风拂槛露华浓,后面再调用get()方法,剩余的就是remainBuffer

        //继续把buffer缓冲区的内容读到字符数组
        //buffer.get(dst);这时候是会报错的
        //当缓冲区的数据量不足以填满整个数组时,会抛出异常,剩下的是"春风拂槛露华浓",只有7个字符
        //此时可以调用get的重载,可以指定缓冲区长度
        buffer.get(dst,0,buffer.remaining());
        System.out.println(dst);
    }
}

4)、直接字节缓冲区

  • 在硬盘中和操作系统中处理的数据都是01二进制,缓冲区中只有ByteBuffer字节缓冲区有资格参与IO操作,channel通道只能通过ByteBuffer作为它的参数
  • 直接字节缓冲区通常是I/O操作最好的选择,如果使用非直接字节缓冲区,可能会导致性能损耗。
  • 如果向通道传到一个非直接字节缓冲区,通道可能会创建一个临时的直接字节缓冲区,将非直接字节缓冲区的内容复制到临时的直接字节缓冲区,使用临时的直接字节缓冲区执行底层的I/O操作
  • 直接字节缓冲区是I/O的最佳选择,可能创建直接缓冲区比创建非直接缓冲区的成本要高,直接缓冲区使用的内存是通过调用本地操作系统的代码分配的,绕过了JVM的堆栈,现在JVM可能会执行缓冲区缓存的优化
	//创建直接字节缓冲区
    ByteBuffer.allocateDirect()

三、Channel

1、channel概述

  • Channel是一种新的I/O访问方式,用于在字节缓冲区与通道另一侧的实体(可以是文件也可以是socket套接字)之间进行传输数据
  • channel可以双向读写数据,也可以实现异步读写
  • 程序不能直接访问channel,channel只能与buffer缓冲区进行交互,即把通道中的数据读到Buffer缓冲区中,程序从缓冲区中读取数据;在写操作时,程序把数据写入到buffer缓冲区中,再把buffer中数据写入到channel中
  • 常用的channel有FileChannel:读写文件;SocketChannel/ServerSocketChannel:读写socket套接字的;DatagramChannel:通过UDP读写网络数据

2、Scatter/Gather

  • scatter(发散)、gather(聚集)是通道提供的重要功能(有时也称为矢量I/O),是指在多个缓冲区中实现一个简单的I/O操作
  • scatter:从channel中读取数据,把这些数据按顺序分散写入到多个Buffer缓冲区中
ByteBuffer buf1 = ByteBuffer.allocate(48);
ByteBuffer buf2 = ByteBuffer.allocate(1024);
ByteBuffer[] bufArray = {buf1,buf2};
Channel.read(bufArray)
read()方法从channel中读取数据,按照在数组中的顺序依次存储到缓冲区中,注意必须在buf1缓冲区满后才能写入buf2.使用scatter/gather处理的数据大小都是固定的
  • gather:写操作时,将多个Buffer缓冲区的数据写入到同一个Channel中
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我爱夜来香A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值