netty的基本概念-NIO三件套

本文详细介绍了Java NIO中的核心对象——缓冲区(Buffer)、选择器(Selector)和通道(Channel)。重点讨论了缓冲区的使用,包括Buffer操作API、原理、分配、分片、只读缓冲区和直接缓冲区。通过实例展示了缓冲区状态的变化,如position、limit和capacity的交互。同时提到了内存映射文件在提高I/O性能上的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java NIO 三件套

在 NIO 中有几个核心对象需要掌握:缓冲区(Buffer)、选择器(Selector)、通道(Channel)

缓冲区 Buffer

1.Buffer 操作基本 AP

在 NIO 中,所有的缓冲区类型都继承于抽象类 Buffer,最常用的就是 ByteBuffer,对于 Java 中的基本类型,基本都有一个具体 Buffer 类型与之相对应,它们之间的继承关系如下图所示:

下面是一个简单的使用 IntBuffer 的例子:

package com.gupaoedu.vip.netty.io.nio.buffer; 
​
import java.nio.IntBuffer; 
​
public class IntBufferDemo { 
​
public static void main(String[] args) { 
// 分配新的 int 缓冲区,参数为缓冲区容量 
// 新缓冲区的当前位置将为零,其界限(限制位置)将为其容量。它将具有一个底层实现数组,其数组偏移量将为零。 
    IntBuffer buffer = IntBuffer.allocate(8); 
    for (int i = 0; i < buffer.capacity(); ++i) { 
        int j = 2 * (i + 1); 
        // 将给定整数写入此缓冲区的当前位置,当前位置递增 
        buffer.put(j); 
    }
    // 重设此缓冲区,将限制设置为当前位置,然后将当前位置设置为 0 
    buffer.flip(); 
    // 查看在当前位置和限制位置之间是否有元素 
    while (buffer.hasRemaining()) { 
        // 读取此缓冲区当前位置的整数,然后当前位置递增 
        int j = buffer.get(); 
        System.out.print(j + " "); 
    }
    } 
}
运行后可以看到:2 4 6 8 10 12 14 16

2.Buffer 的基本的原理

在谈到缓冲区时,我们说缓冲区对象本质上是一个数组,但它其实是一个特殊的数组,缓冲区对象内置了一些机制, 能够跟踪和记录缓冲区的状态变化情况,如果我们使用 get()方法从缓冲区获取数据或者使用 put()方法把数据写入缓冲 区,都会引起缓冲区状态的变化。

在缓冲区中,最重要的属性有下面三个,它们一起合作完成对缓冲区内部状态的变化跟踪:

position:指定下一个将要被写入或者读取的元素索引,它的值由 get()/put()方法自动更新,在新创建一个 Buffer 对象 时,position 被初始化为 0。

limit:指定还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。

capacity:指定了可以存储在缓冲区中的最大数据容量,实际上,它指定了底层数组的大小,或者至少是指定了准许我们使用的底层数组的容量。

以上三个属性值之间有一些相对大小的关系:0 <= position <= limit <= capacity。如果我们创建一个新的容量大小为 10 的 ByteBuffer 对象,在初始化的时候,position 设置为 0,limit 和 capacity 被设置为 10,在以后使用 ByteBuffer 对象过程中,capacity 的值不会再发生变化,而其它两个个将会随着使用而变化。

下面我们用代码来演示一遍,准备一个 txt 文档,存放的 E 盘,输入以下内容:

Tom.

下面我们用一段代码来验证 position、limit 和 capacity 这几个值的变化过程,代码如下:

public class BufferDemo { 
    public static void main(String args[]) throws Exception { 
        //这用用的是文件 IO 处理 
        FileInputStream fin = new FileInputStream("E://test.txt"); 
        //创建文件的操作管道 
        FileChannel fc = fin.getChannel(); 
        //分配一个 10 个大小缓冲区,说白了就是分配一个 10 个大小的 byte 数组 
        ByteBuffer buffer = ByteBuffer.allocate(10); 
        output("初始化", buffer); 
        //先读一下 
        fc.read(buffer); 
        output("调用 read()", buffer); 
        //准备操作之前,先锁定操作范围 
        buffer.flip(); 
        output("调用 flip()", buffer); 
        //判断有没有可读数据 
        while (buffer.remaining() > 0) { 
        byte b = buffer.get(); 
        // System.out.print(((char)b)); 
        }
        output("调用 get()", buffer); 
        //可以理解为解锁 
        buffer.clear(); 
        output("调用 clear()", buffer); 
        //最后把管道关闭 
        fin.close(); 
    }
    //把这个缓冲里面实时状态给答应出来 
    public static void output(String step, Buffer buffer) { 
        System.out.println(step + " : "); 
        //容量,数组大小 
        System.out.print("capacity: " + buffer.capacity() + ", "); 
        //当前操作数据所在的位置,也可以叫做游标 
        System.out.print("position: " + buffer.position() + ", "); 
        //锁定值,flip,数据操作范围索引只能在 position - limit 之间 
        System.out.println("limit: " + buffer.limit()); 
        System.out.println(); 
    } 
}

完成的输出结果为:

运行结果我们已经看到,下面呢对以上结果进行图解,四个属性值分别如图所示:

我们可以从通道中读取一些数据到缓冲区中,注意从通道读取数据,相当于往缓冲区中写入数据。如果读取 4 个自己的数据,则此时 position 的值为 4,即下一个将要被写入的字节索引为 4,而 limit 仍然是 10,如下图所示:

下一步把读取的数据写入到输出通道中,相当于从缓冲区中读取数据,在此之前,必须调用 flip()方法,该方法将会完成两件事情:

\1. 把 limit 设置为当前的 position 值

\2. 把 position 设置为 0

由于 position 被设置为 0,所以可以保证在下一步输出时读取到的是缓冲区中的第一个字节,而 limit 被设置为当前的position,可以保证读取的数据正好是之前写入到缓冲区中的数据,如下图所示:

在从缓冲区中读取数据完毕后,limit 的值仍然保持在我们调用 flip()方法时的值,调用 clear()方法能够把所有的状态变化设置为初始化时的值,如下图所示:

3.缓冲区的分配

在前面的几个例子中,我们已经看过了,在创建一个缓冲区对象时,会调用静态方法 allocate()来指定缓冲区的容量,其实调用 allocate()相当于创建了一个指定大小的数组,并把它包装为缓冲区对象。或者我们也可以直接将一个现有的数组,包装为缓冲区对 象,如下示例代码所示:

/** 手动分配缓冲区 */ 
​
public class BufferWrap { 
    public void myMethod() { 
        // 分配指定大小的缓冲区 
        ByteBuffer buffer1 = ByteBuffer.allocate(10); 
        //包装一个现有的数组 
        byte array[] = new byte[10]; 
        ByteBuffer buffer2 = ByteBuffer.wrap( array ); 
    } 
}

4.缓冲区分片

在 NIO 中,除了可以分配或者包装一个缓冲区对象外,还可以根据现有的缓冲区对象来创建一个子缓冲区,即在现有缓冲区上切 出一片来作为一个新的缓冲区,但现有的缓冲区与创建的子缓冲区在底层数组层面上是数据共享的,也就是说,子缓冲区相当于是 现有缓冲区的一个视图窗口。调用 slice()方法可以创建一个子缓冲区,让我们通过例子来看一下

/**
\* 缓冲区分片 
*/ 
public class BufferSlice { 
    static public void main( String args[] ) throws Exception { 
        ByteBuffer buffer = ByteBuffer.allocate( 10 ); 
        // 缓冲区中的数据 0-9 
        for (int i=0; i<buffer.capacity(); ++i) { 
            buffer.put( (byte)i ); 
        }
        // 创建子缓冲区 
        buffer.position( 3 ); 
        buffer.limit( 7 ); 
        ByteBuffer slice = buffer.slice(); 
        // 改变子缓冲区的内容 
        for (int i=0; i<slice.capacity(); ++i) { 
            byte b = slice.get( i ); 
            b *= 10; 
            slice.put( i, b ); 
        }
        buffer.position( 0 ); 
        buffer.limit( buffer.capacity() ); 
        while (buffer.remaining()>0) { 
        System.out.println( buffer.get() ); 
        } 
    } 
}

在该示例中,分配了一个容量大小为 10 的缓冲区,并在其中放入了数据 0-9,而在该缓冲区基础之上又创建了一个子缓冲区,并 改变子缓冲区中的内容,从最后输出的结果来看,只有子缓冲区“可见的”那部分数据发生了变化,并且说明子缓冲区与原缓冲区是数据共享的,输出结果如下所示:

5.只读缓冲区

只读缓冲区非常简单,可以读取它们,但是不能向它们写入数据。可以通过调用缓冲区的 asReadOnlyBuffer()方法,将任何常规缓冲区转 换为只读缓冲区,这个方法返回一个与原缓冲区完全相同的缓冲区,并与原缓冲区共享数据,只不过它是只读的。如果原缓冲区的内容发生了变化,只读缓冲区的内容也随之发生变化:

/** 只读缓冲区 */ 
public class ReadOnlyBuffer { 
    static public void main( String args[] ) throws Exception { 
    ByteBuffer buffer = ByteBuffer.allocate( 10 ); 
    // 缓冲区中的数据 0-9 
    for (int i=0; i<buffer.capacity(); ++i) { 
        buffer.put( (byte)i ); 
    }
    // 创建只读缓冲区 
    ByteBuffer readonly = buffer.asReadOnlyBuffer(); 
    // 改变原缓冲区的内容 
    for (int i=0; i<buffer.capacity(); ++i) { 
        byte b = buffer.get( i ); 
         b *= 10; 
         buffer.put( i, b ); 
    }
    readonly.position(0); 
    readonly.limit(buffer.capacity()); 
    // 只读缓冲区的内容也随之改变 
    while (readonly.remaining()>0) { 
         System.out.println( readonly.get()); 
    }
    } 
}

6.直接缓冲区

直接缓冲区是为加快 I/O 速度,使用一种特殊方式为其分配内存的缓冲区,JDK 文档中的描述为:给定一个直接字节缓冲区,Java虚拟机将尽最大努力直接对它执行本机 I/O 操作。也就是说,它会在每一次调用底层操作系统的本机 I/O 操作之前(或之后),尝试

避免将缓冲区的内容拷贝到一个中间缓冲区中或者从一个中间缓冲区中拷贝数据。要分配直接缓冲区,需要调用 allocateDirect()方法,而不是 allocate()方法,使用方式与普通缓冲区并无区别,如下面的拷贝文件示例:

/**
\* 直接缓冲区 
*/ 
public class DirectBuffer { 
    static public void main( String args[] ) throws Exception { 
        //首先我们从磁盘上读取刚才我们写出的文件内容 
        String infile = "E://test.txt"; 
        FileInputStream fin = new FileInputStream( infile ); 
        FileChannel fcin = fin.getChannel(); 
        //把刚刚读取的内容写入到一个新的文件中 
        String outfile = String.format("E://testcopy.txt"); 
        FileOutputStream fout = new FileOutputStream( outfile ); 
        FileChannel fcout = fout.getChannel(); 
        // 使用 allocateDirect,而不是 allocate 
        ByteBuffer buffer = ByteBuffer.allocateDirect(1024); 
        while (true) { 
            buffer.clear(); 
            int r = fcin.read(buffer); 
            if (r==-1) { 
                break; 
            }
            buffer.flip(); 
            fcout.write(buffer); 
        } 
    } 
}

7.内存映射

内存映射是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快的多。内存映射文件 I/O 是通过使文件中的数据出现为 内存数组的内容来完成的,这其初听起来似乎不过就是将整个文件读到内存中,但是事实上并不是这样。一般来说,只有文件中实际读取或者写入的部分才会映射到内存中。如下面的示例代码:

/**
\* IO 映射缓冲区 
*/ 
public class MappedBuffer { 
    static private final int start = 0; 
    static private final int size = 1024; 
    static public void main( String args[] ) throws Exception { 
        RandomAccessFile raf = new RandomAccessFile( "E://test.txt", "rw" ); 
        FileChannel fc = raf.getChannel(); 
        //把缓冲区跟文件系统进行一个映射关联 
        //只要操作缓冲区里面的内容,文件内容也会跟着改变 
        MappedByteBuffer mbb = fc.map( FileChannel.MapMode.READ_WRITE,start, size ); 
        mbb.put( 0, (byte)97 ); 
        mbb.put( 1023, (byte)122 ); 
        raf.close(); 
    } 
}

 

<think>嗯,用户需要一份技术概要,涵盖计算机基础Java核心技术点。用户明确列出了IO、多线程、并发、缓存、分布式、Java集合、反射、NIO、JVM原理GC机制这些关键领域。这看起来像是为面试或技能梳理做的准备。 从引用内容来看,用户参考的几份资料都是Java面试题库,说明用户可能在准备技术面试。引用[1]提到1658页资料覆盖了JVM/多线程等核心内容,引用[2][4]则细分了MySQL/Redis等具体技术的面试题。不过用户当前需求更聚焦在知识体系梳理而非具体题目。 用户的技术要求可以分为两个层面: 1)计算机基础部分需要解释IO模型、线程进程区别、并发控制手段、缓存策略、分布式核心概念 2)Java部分要突出集合框架的设计、NIO的优势、反射机制的风险点、JVM内存结构GC算法 特别要注意的是,用户提到“整理技术概要”而非深入讲解某个点,所以回复应该保持结构化但不过于深入。每个技术点用一两句话说明核心即可,比如解释BIO/NIO区别时用“阻塞vs非阻塞”这样的关键词。 在组织内容时,我会把计算机基础Java技术分成两大块。计算机基础按IO->多线程->并发->缓存->分布式的逻辑链条排列,Java部分则按集合->IO->反射->NIO->JVM的顺序,这样符合从基础到高级的认知规律。JVM作为重点单独列出,因为用户特别强调了要“熟悉原理GC机制”。 最后考虑补充相关问题,帮助用户自我检测。问题设计要覆盖所有技术点但避免重复,比如: - 分布式锁的实现方式(对应分布式章节) - ConcurrentHashMap的并发机制(衔接Java集合并发) - G1收集器特点(深化JVM知识点) 这样既能检验知识掌握,又体现技术点之间的关联性。</think>以下是根据您的要求整理的技术概要,涵盖计算机基础知识Java核心技术栈,结合主流面试考点实际应用场景: ### 一、计算机基础核心 1. **I/O模型** - 阻塞/非阻塞I/O:BIO vs NIO - 多路复用:`select/poll/epoll`原理(Linux) - 异步I/O:`AIO`实现原理 - 应用场景:高并发网络服务器设计[^4] 2. **多线程与并发** - 线程生命周期:新建、就绪、运行、阻塞、终止 - 线程同步机制: ```java synchronized // 对象锁 ReentrantLock // 可重入锁 Semaphore // 信号量 ``` - 并发容器:`ConcurrentHashMap`分段锁,`CopyOnWriteArrayList`写时复制[^2] 3. **分布式系统** - CAP定理:一致性、可用性、分区容错性权衡 - 分布式事务:2PC、TCC、Saga模式 - 服务治理:熔断(Hystrix)、限流(Sentinel)[^4] 4. **缓存技术** - 缓存穿透:布隆过滤器解决方案 - 缓存雪崩:随机过期时间+多级缓存 - Redis核心:单线程Reactor模型、持久化(RDB/AOF)[^2][^4] ### 二、Java核心技术栈 1. **集合框架** - `HashMap`:数组+链表/红黑树(JDK8+),负载因子0.75 - `ConcurrentHashMap`:分段锁(JDK7) vs CAS+synchronized(JDK8) - `ArrayList` vs `LinkedList`:随机访问$O(1)$ vs 插入删除$O(1)$[^1][^2] 2. **I/NIO** - BIO:线程-per-connection模型(`ServerSocket`) - NIO:Channel/Buffer/Selector三件套 ```java Selector selector = Selector.open(); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); ``` - Netty:基于事件驱动的Reactor模式实现[^4] 3. **反射机制** - 动态代理:`Proxy.newProxyInstance()`生成代理对象 - 应用场景:Spring AOP、MyBatis Mapper映射 - 性能隐患:直接调用比反射快100+倍[^2] 4. **JVM原理** - 内存模型: ``` ┌─────────┐ ┌──────────┐ ┌───────┐ │ 方法区 │ │ 堆内存 │ │ 栈内存 │ └─────────┘ └──────────┘ └───────┘ ``` - 类加载:加载→验证→准备→解析→初始化 - GC算法: - 标记-清除(老年代CMS) - 复制算法(新生代) - G1:分Region收集+预测停顿时间[^1][^4] 5. **GC调优** - 监控工具:`jstat -gcutil`、VisualVM - 参数示例: ``` -XX:+UseG1GC -Xmx4g -Xms4g -XX:MaxGCPauseMillis=200 ``` - OOM排查:MAT分析堆转储文件[^4] --- ### 三、技术关联场景 | 技术组合 | 应用场景 | |--------------------|--------------------------| | NIO+多线程+Redis | 电商秒杀系统 | | JVM调优+GC算法 | 高吞吐量金融交易系统 | | 分布式+缓存 | 社交平台热点数据推送 | --- ### 相关问题 1. 如何设计一个支持10万并发的NIO服务器? 2. `ConcurrentHashMap`在JDK7JDK8中的实现差异是什么? 3. G1收集器如何实现可预测的停顿时间? 4. Redis缓存穿透有哪些解决方案? 5. 分布式事务中TCC模式的实现原理是什么? > 参考资料: > [^1]: Java面试核心技术栈:JVM/多线程/集合框架等底层原理 > [^2]: 分布式/缓存/高并发场景解决方案 > [^4]: JVM调优实战与性能监控工具解析
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值