java中的nio

  • 在java网络编程中,最常用的就是nio的一些工具类,今天先介绍一些nio的相关内容。

一、三大组件

1、channel:渠道,可以理解为通信的通道

2、buffer:数据流,可以理解为通信时候传输数据的载体

3、selector:多路复用器,也是nio的效率的保障

channel

常见的channel一共有四种

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

FileChannel用户文件传输,其他三个都是网络通信使用

buffer

常见的buffer分为一下几种

  • ByteBuffer:MappedByteBuffer、DirectByteBuffer、HeapByteBuffer
  • 类型Buffer

最常用的是ByteBuffer,其他的Buffer可以暂时不考虑。

HeapByteBuffer和DirectByteBuffer的区别:

① HeapByteBuffer初始化使用allocate()方法,使用的堆内存,会受到GC的影响,分配速度快,读写效率低

② DirectByteBuffer初始化使用allocateDirect()方法,使用真实内存,不会受到GC影响,分配速度慢,读写效率高

首选需要了解byteBuffer内部结构,同时在使用的时候需要注意,读写的切换。

  • capacity:缓冲区的容量,一旦创建,就没有办法修改
  • limit:缓冲区的界限,limit之后的数据是不可读的
  • position:读写的索引位置
  • mark:记录当前position的位置,通过mark()方法记录后,reset()方法恢复到mark记录的位置

四个属性满足条件是:mark<=position<=limit<=capacity

简单使用代码:

public class TestByteBuffer {
    public static void main(String[] args) {
        // 获得FileChannel
        try (FileChannel channel = new FileInputStream("stu.txt").getChannel()) {
            // 获得缓冲区
            ByteBuffer buffer = ByteBuffer.allocate(10);
            int hasNext = 0;
            StringBuilder builder = new StringBuilder();
            while((hasNext = channel.read(buffer)) > 0) {
                // 切换模式 limit=position, position=0
                buffer.flip();
                // 当buffer中还有数据时,获取其中的数据
                while(buffer.hasRemaining()) {
                    builder.append((char)buffer.get());
                }
                // 切换模式 position=0, limit=capacity
                buffer.clear();
            }
            System.out.println(builder.toString());
        } catch (IOException e) {
        }
    }
}

1、allocate 分配一个byteBuffer ,大小为10

2、循环查看channel中是否还存在数据,如果存在继续使用channle.read(buffer)读到buffer中

3、buffer.filp(),切换到读模式

4、循环从buffer中读出数据,并写入到其他地方

核心API:

1、put():向byteBuffer中放入数据,position+1,同时limit=capcaity,结构如下:

2、flip():把byteBuffer切换成写模式,这个时候position=0,limit=3,结构如下:

3、get():从byteBuffer中读取一个元素,当未指定下标的时候,position+1,当执行下标的时候也就是get(i),position位置不变,读取一个元素后结构如下:

 4、rewind():恢复position=0,其它值不变,这个时候可以从头读,或者切换到写模式覆盖,结构如下:

5、clear():恢复position、limit、capacity的初始位置,但是数据不清空,结构如下:

6、mark()、reset():mark会记录当前position的值,reset会恢复mark记录的position的值。

7、compact():该方法是把已读的数据弹出,未读的数据向前压缩,但是未读数据的原位置保存的还是原来的数据,写的时候就会覆盖,同时切换到写模式(可理解为接着写),结构如下:

可以看到,4、5虽然向前压缩了,但是原来的位置还是存放的4、5,但是position的位置上变为写的位置,所以4、5会被覆盖。

clear和compact比较:

① clear是清空,compact是接着写

② 因为涉及到数据的移动,所以compact更加消耗性能

String和byteBuffer相互转换

1、String转换为ByteBuffer:

  • str.getBytes();
  • StandardCharsets.UTF_8.encode("abc")
  • ByteBuffer.wrap("abc".getBytes(StandardCharsets.UTF_8));

2、ByteBuffer转换为String:

  • StandardCharsets.UTF_8.decode(wrap).toString();

selector

1、单线程处理网络连接:

  • 连接数多的时候,会造成线程开启太多,内存占用高
  • 线程之间切换消耗很大

2、线程池处理网络连接:

  • 线程需要等待第一个连接完成后,才能处理第二个连接,适合短连接的情况,
  • 当一个连接处理未完成的时候,其它连接都是阻塞的

3、多路复用器selector:

        Selector的作用就是用来轮询每个注册的Channel,一旦发现Channel有注册的事件发生,便获取事件然后进行处理。这样一来,只是用一个单线程就可以管理多个通道,也就是管理多个连接。这样使得只有在连接真正有读写事件发生时,才会调用函数来进行读写,就大大地减少了系统开销,并且不必为每个连接都创建一个线程,不用去维护多个线程,并且避免了多线程之间的上下文切换导致的开销。

二、黏包与半包

        因为网络传输中,会把数据积攒到一定量之后一次性的发送出去,这样就会出现一次性发送的数据,一次性发送多条数据,这就是黏包,同样的也可能一次性发送数据含有下一条数据的一半,这就是半包。其实知道这个概念之后,就知道怎么处理了,在读写的时候需要注意一下即可,下面是一个简单的解决小例子:

public class ByteBufferDemo {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(32);
        // 模拟粘包+半包
        buffer.put("Hello,world\nI'm Nyima\nHo".getBytes());
        // 调用split函数处理
        split(buffer);
        buffer.put("w are you?\n".getBytes());
        split(buffer);
    }

    private static void split(ByteBuffer buffer) {
        // 切换为读模式
        buffer.flip();
        for(int i = 0; i < buffer.limit(); i++) {

            // 遍历寻找分隔符
            // get(i)不会移动position
            if (buffer.get(i) == '\n') {
                // 缓冲区长度
                int length = i+1-buffer.position();
                ByteBuffer target = ByteBuffer.allocate(length);
                // 将前面的内容写入target缓冲区
                for(int j = 0; j < length; j++) {
                    // 将buffer中的数据写入target中
                    target.put(buffer.get());
                }
                // 打印查看结果
                ByteBufferUtil.debugAll(target);
            }
        }
        // 切换为写模式,但是缓冲区可能未读完,这里需要使用compact
        buffer.compact();
    }
}

然后,就是还有一个思想,数据分而治之,就是把数据分开处理,不论读写都可以分段的去处理,其实在平时处理大批数据的时候也会用到这个思想。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值