Java NIO基础使用

前言

Java中的输入输出操作主要针对数据文件和Socket对象,传统的Java IO操作都是阻塞进行的,比如在读取网络数据的时候如果数据还没有返回那么read方法就会被阻塞一直等到网络数据返回或者发生错误,再读取的时候也采用流式读取数据。在Java1.4中引入了NIO处理输入输出,NIO采用内存映射文件的方式将文件或文件的一段映射到内存中,同时它使用块IO来处理数据效率更高。

缓冲区

缓冲区对象其实就是容器对象,在原来的IO操作中所有的数据读写都是直接通过Stream对象来完成,在NIO中读取数据需要放到缓冲区中,写入数据也需要放到缓冲区中。Java中所有的基础类型都有对应的缓冲区对象,ByteBuffer、DoubleBuffer、IntBuffer等。
这里写图片描述
缓冲区本质上来说是数组对象,它有三个重要的属性position、limit和capacity,它们的值有(0 <= position <= limit <= capacity)的大小关系。其中position作为操作的开始位置,不管是读取get操作还是写入put操作,position都会比自动更新;limit表明操作position时最多能够操作到的位置,capacity代表当前的缓冲区大小值。

public static void main(String[] args) {
    IntBuffer intBuffer = IntBuffer.allocate(10);
    for (int i = 0; i < intBuffer.capacity() - 3; i++) {
        intBuffer.put(i * 5);
    }

    intBuffer.flip();
    while (intBuffer.hasRemaining()) {
        System.out.print(intBuffer.get() + " ");
    }
}
// 0 5 10 15 20 25 30

上面的示例中IntBuffer由于是抽象类无法被实例化,就使用IntBuffer提供的静态方法初始化生成子类HeapIntBuffer对象,初始化它的容量为10个Int值,接下来在intBuffer对象中防止7个整数值,这时position=7,limit=10,后面通过flip调用将position=0,limit=7,hasRemaining方法不断比较position和limit属性大小,get方法会增加position大小,当position=limit代表数据完全读取完毕。

缓冲区的数据作为数组存放在JVM堆内存中,不过不同的JVM中数组对象的储存格式有所不同,为此引入了直接缓冲区,直接缓冲区位于JVM堆内存之外,它的请求和释放不参与GC,直接缓冲区使用的内存是通过调用本地操作系统的代码分配的,绕过了标准JVM堆栈。不过通过下面的Demo测试发现分配的内存被算到了堆内存容量中。

public static void main(String[] args) {
    // 直接分配10M内存
    ByteBuffer byteBuffer = ByteBuffer.allocateDirect(10 * 1024 * 1024);
    System.out.println("Allocate Successfully!");
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

java -Xms5m -Xmx5m NewIOTest
Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory
        at java.nio.Bits.reserveMemory(Bits.java:693)
        at java.nio.DirectByteBuffer.<init>(DirectByteBuffer.java:123)
        at java.nio.ByteBuffer.allocateDirect(ByteBuffer.java:311)
        at NewIOTest.main(NewIOTest.java:5)

java -Xms15m -Xmx15m NewIOTest
Allocate Successfully!

通道

通道类似旧IO操作中的Stream对象,不过通道对象能够读也能够写,通道只能读取缓冲区中的数据获取将数据写入到缓冲区中。通道对象代表的是数据文件或者Socket等与程序之间的连接通路,因而所有的Channel都不是通过构造器创建的,而是通过传统的节点InputStream、OutputStream的getChannel方法来返回响应的Channel。

FileInputStream fis = new FileInputStream("Main.java");
FileChannel fileChannel = fis.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(2048);
fileChannel.read(byteBuffer);
byteBuffer.flip();
while (byteBuffer.hasRemaining()) {
    System.out.print((char) byteBuffer.get());
}

选择子

Selector能够检测多个注册的通道上是否有事件发生,如果有事件发生,便获取事件然后针对每个事件进行相应的响应处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。这里通过实现一个简单的服务器只需要每次将用户输入的内容添加“Hello”之后返回到客户端。

public static void main(String[] args) throws Exception {
    ServerSocketChannel socketChannel = ServerSocketChannel.open();
    // 设置Socket是非阻塞的
    socketChannel.configureBlocking(false);
    // 绑定到5555端口
    socketChannel.bind(new InetSocketAddress(5555));
    Selector selector = Selector.open();
    // 为服务器注册接收事件
    socketChannel.register(selector, SelectionKey.OP_ACCEPT);

    while (true) {
        int count = selector.select();
        if (count <= 0) continue;
        Set<SelectionKey> selectionKeySet = selector.selectedKeys();
        Iterator<SelectionKey> iterable = selectionKeySet.iterator();
        while (iterable.hasNext()) {
            SelectionKey key = iterable.next();
            if (key.isAcceptable()) {
                // 如果accept被激活,说明有客户端连接
                SocketChannel newSocketChannel = socketChannel.accept();
                newSocketChannel.configureBlocking(false);
                // 为客户端连接添注册读事件
                newSocketChannel.register(selector, SelectionKey.OP_READ);
            } else if (key.isReadable()) {
                // 如果读到客户端的数据
                SocketChannel clientSocket = (SocketChannel) key.channel();
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                clientSocket.read(byteBuffer);
                System.out.println(new String(byteBuffer.array()));
                String text = "Hello: " + new String(byteBuffer.array())  + "\r\n";
                // 将Hello + 客户端数据返回给用户
                clientSocket.write(ByteBuffer.wrap(text.getBytes()));
            }
            iterable.remove();
        }
    }
}
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值