栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Netty基础入门——NIO相关补充

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Netty基础入门——NIO相关补充

文章目录
  • 1. 前言
  • 2. NIO源码初探
    • 2.1 缓冲区补充
      • 2.1.1 只读缓冲区(asReadOnlyBuffer)
      • 2.1.2 直接缓冲区(allocateDirect)
      • 2.1.3 内存映射
    • 2.2 Selector
  • 3. 其他
    • 3.1 访问修饰符的作用范围
    • 3.2 使用递归来反转字符串
    • 3.3 使用递归来逆序一个栈

1. 前言

这里再次强调一个概念:

所谓“高并发”是指1ms内至少同时有上千个连接请求准备好。

在前面的几篇博客中,我们已经可以简单的使用NIO来实现一些简单的消息收发。在这篇博客中,将看看其实现源码。

2. NIO源码初探

在NIO中有三个核心对象:缓冲区(Buffer)、选择器(Selector)和通道(Channel)。

对于缓冲区这里补充两个之前没有使用的缓冲区和内存映射。

2.1 缓冲区补充 2.1.1 只读缓冲区(asReadOnlyBuffer)

可以通过调用缓冲区的asReadonlyBuffer()方法,将任何常规缓冲区转换为只读缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区,并与原缓冲区共享数据。和其名称一样只读缓冲区可以读取它们,但是不能向它们写入数据。需要注意的是,如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化。比如下面的案例:

public class ReadOnlyBufferDemo {
    public static void main(String[] args) {
        String message = "Hello!";
        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        byteBuffer.put(message.getBytes());

        ByteBuffer readonlyBuffer = byteBuffer.asReadOnlyBuffer();

        // 改变原缓冲区的内容
        byteBuffer.clear();
        byteBuffer.put("World_Hello!".getBytes());
        byteBuffer.flip();

        // 读取只读ByteBuffer
        readOnlyBuffer.flip();
        byte[] datas = new byte[readOnlyBuffer.limit()];
        int index = 0;
        while (readOnlyBuffer.remaining() > 0) {
            datas[index++] = readOnlyBuffer.get();
        }
        System.out.println("消息内容:" + new String(datas, 0, datas.length));
    }
}

结果:

从上面的案例中可以得出两个结论:

  • 如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化。但是如果将只读的缓冲区传递给调用程序,在一定程度上对数据具有保护作用。
  • 只读缓冲区的范围在调用asReadonlyBuffer()方法之后,其数据范围就固定了,对应的就是原数据的position和limit。

需要注意的是,只可以把常规缓冲区转换为只读缓冲区,而不能将只读缓冲区转换为可写的缓冲区。

2.1.2 直接缓冲区(allocateDirect)

下面为了测试,以文件拷贝的时间来比较效率:

public class DirectBufferDemo {

    private static final String PATH = "D://a.png";

    public static void main(String[] args) {
        long time_1 = System.currentTimeMillis();
        copyFileByBuffer();
        System.out.println("使用缓冲区文件拷贝时间:"+ (System.currentTimeMillis() - time_1));

        long time_2 = System.currentTimeMillis();
        copyFileByDirectBuffer();
        System.out.println("使用直接缓冲区文件拷贝时间:" + (System.currentTimeMillis() - time_2));
    }

    public static void copyFileByDirectBuffer(){
        FileOutputStream fileOutputStream = null;
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(PATH);
            fileOutputStream = new FileOutputStream("b.png");
            FileChannel inputChannel = fileInputStream.getChannel();
            FileChannel outputChannel = fileOutputStream.getChannel();
            // 这里使用allocateDirect
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(512);
            int len = -1;
            while((len = inputChannel.read(byteBuffer)) != -1){
                byteBuffer.flip();
                outputChannel.write(byteBuffer);
                byteBuffer.clear();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fileOutputStream != null) {
                    fileOutputStream.close();
                }
                if(fileInputStream != null){
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void copyFileByBuffer(){
        FileOutputStream fileOutputStream = null;
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(PATH);
            fileOutputStream = new FileOutputStream("a.png");
            FileChannel inputChannel = fileInputStream.getChannel();
            FileChannel outputChannel = fileOutputStream.getChannel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(512);
            int len = -1;
            while((len = inputChannel.read(byteBuffer)) != -1){
                byteBuffer.flip();
                outputChannel.write(byteBuffer);
                byteBuffer.clear();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fileOutputStream != null) {
                    fileOutputStream.close();
                }
                if(fileInputStream != null){
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

结果:

2.1.3 内存映射
public class MappedBufferDemo {

    private static final String PATH = "D://a.png";

    public static void main(String[] args) {
        long time_1 = System.currentTimeMillis();
        copyFileByBuffer();
        System.out.println("使用缓冲区文件拷贝时间:"+ (System.currentTimeMillis() - time_1));

        long time_2 = System.currentTimeMillis();
        copyFileByMappedBuffer();
        System.out.println("使用IO映射缓冲区文件拷贝时间:" + (System.currentTimeMillis() - time_2));
    }

    public static void copyFileByMappedBuffer(){
        FileOutputStream fileOutputStream = null;
        FileInputStream fileInputStream = null;
        try {
            File file = new File(PATH);
            fileInputStream = new FileInputStream(file);
            fileOutputStream = new FileOutputStream("b.png");
            FileChannel inputChannel = fileInputStream.getChannel();
            FileChannel outputChannel = fileOutputStream.getChannel();

            MappedByteBuffer buffer = null;

            int len = 0, length = (int) file.length();
            while(len < length){
                if(length - len > 1024){
                    buffer = inputChannel.map(FileChannel.MapMode.READ_ONLY, len, 1024);
                    len += 1024;
                } else{
                    buffer = inputChannel.map(FileChannel.MapMode.READ_ONLY, len, length - len);
                    len = length;
                }
                // buffer.flip(); 因为本身是可读的,所以不需要flip()
                outputChannel.write(buffer);
                buffer.clear();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fileOutputStream != null) {
                    fileOutputStream.close();
                }
                if(fileInputStream != null){
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static void copyFileByBuffer(){
        FileOutputStream fileOutputStream = null;
        FileInputStream fileInputStream = null;
        try {
            fileInputStream = new FileInputStream(PATH);
            fileOutputStream = new FileOutputStream("a.png");
            FileChannel inputChannel = fileInputStream.getChannel();
            FileChannel outputChannel = fileOutputStream.getChannel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(512);
            int len = -1;
            while((len = inputChannel.read(byteBuffer)) != -1){
                byteBuffer.flip();
                outputChannel.write(byteBuffer);
                byteBuffer.clear();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if(fileOutputStream != null) {
                    fileOutputStream.close();
                }
                if(fileInputStream != null){
                    fileInputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

2.2 Selector

在使用NIO多路复用的时候,我们需要使用Selector.open()方法来得到一个Selector实例。方法的背后:

public static Selector open() throws IOException {
    return SelectorProvider.provider().openSelector();
}

因为所用的系统为Windows,这里JDK中所使用的SelectorProvider为WindowsSelectorProvider。即通过provider()方法得到一个WindowsSelectorProvider对象,然后使用openSelector()方法来创建一个WindowsSelectorImpl的Selector对象。


3. 其他

一些遗忘的知识点:

3.1 访问修饰符的作用范围
public class SelectorDemo {

    public static int one = 10;
    protected static int value = 10;
    static int other = 10;
    private static int two = 10;
}

需要注意的是,protected和默认的访问修饰符在同包中可以访问。

3.2 使用递归来反转字符串

不引入该方法外的变量。

public String reverse(String str){
    int len = str.length();
    if(len <= 1){
        return str;
    }
    String left = str.substring(0, len / 2);
    String right = str.substring(len / 2, len);
    return reverse(right) + reverse(left);
}
3.3 使用递归来逆序一个栈

因为栈的操作只在一端,所有我们每次只能删除栈顶元素,然后添加新元素到栈顶。

public Stack reverse(Stack stack){
    if(stack.isEmpty()){
        return stack;
    }
    int last = getAndRemoveLastElement(stack); // 取得当前栈底元素
    reverse(stack);
    stack.push(last);
    return stack;
}

public int getAndRemoveLastElement(Stack stack){
    int result = stack.pop();
    if(stack.isEmpty()){
        return result;
    }else{
        int last = getAndRemoveLastElement(stack); //递归到找到最后一个元素,命名为last
        stack.push(result);   //其他名称仍叫result,要重新压入栈
        return last;     //只返回last一个元素
    }
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/684717.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号