深入理解Java IO机制,从容应对面试与实战
一、BIO源码解析
1.1 IO流层次结构
1┌─────────────────────────────────────────────────────────────────────┐2│ Java IO流层次结构图 │3├─────────────────────────────────────────────────────────────────────┤4│ │5│ ┌──────────────┐ │6│ │ InputStream │ │7│ └───────┬──────┘ │8│ ┌───────────┴───────────┐ │9│ │ │ │10│ ┌───────┴───────┐ ┌───────┴───────┐ │11│ │ FileInputStream│ │FilterInputStream│ │12│ │ SocketInput │ │ BufferedInput │ │13│ └───────────────┘ └────────────────┘ │14│ │15│ ┌──────────────┐ │16│ │ OutputStream │ │17│ └───────┬──────┘ │18│ ┌───────────┴───────────┐ │19│ │ │ │20│ ┌───────┴───────┐ ┌───────┴───────┐ │21│ │FileOutputStream│ │FilterOutput │ │22│ └───────────────┘ └────────────────┘ │23│ │24│ ┌──────────────┐ │25│ │ Reader │ │26│ └───────┬──────┘ │27│ ┌───────────┴───────────┐ │28│ ┌───────┴───────┐ ┌───────┴───────┐ │29│ │ FileReader │ │ BufferedReader │ │30│ └───────────────┘ └────────────────┘ │31│ │32└─────────────────────────────────────────────────────────────────────┘1.2 FileInputStream源码解析
java1/**2 * FileInputStream 源码解析3 * 4 * 继承关系:5 * FileInputStream -> InputStream -> Object6 */7publicclass FileInputStream extends InputStream {89 // 文件描述符10privatefinal FileDescriptor fd;1112 // 文件路径13privatefinalString path;1415 // 通道16private FileChannel channel;1718 // 私有属性19privateboolean closed = false;2021 /**22 * 构造方法23 */24publicFileInputStream(String name) throws FileNotFoundException {25this(name != null ? newFile(name) : null);26 }2728publicFileInputStream(File file) throws FileNotFoundException {29String name = (file != null ? file.getPath() : null);30 SecurityManager security = System.getSecurityManager();31if (security != null) {32 security.checkRead(name);33 }34if (name == null) {35thrownewNullPointerException();36 }37if (file.isInvalid()) {38thrownewFileNotFoundException("Invalid file path");39 }40this.fd = newFileDescriptor();41this.path = name;42 // 打开文件,native方法43open(name);44 }4546 /**47 * 打开文件 - native方法48 */49privatenativevoidopen(String name) throws FileNotFoundException;5051 /**52 * 读取字节53 */54publicintread() throws IOException {55 // 同步56synchronized (this) {57ensureOpen();58 }59 // native读取60returnread0();61 }6263 // native方法64privatenativeintread0() throws IOException;6566 /**67 * 读取到字节数组68 */69publicintread(byte[] b, int off, int len) throws IOException {70synchronized (this) {71ensureOpen();72 // 核心读取逻辑73returnreadBytes(b, off, len);74 }75 }7677privatenativeintreadBytes(byte[] b, int off, int len) throws IOException;7879 /**80 * 获取FileChannel81 */82public FileChannel getChannel() {83synchronized (this) {84if (channel == null) {85 channel = FileChannelImpl.open(fd, path, true, false, this);86 }87return channel;88 }89 }9091 /**92 * 关闭流93 */94publicvoidclose() throws IOException {95synchronized (this) {96if (closed) {97return;98 }99 closed = true;100 }101102if (channel != null) {103 channel.close();104 }105106 // native关闭107close0();108 }109110privatenativevoidclose0() throws IOException;111}1.3 Buffer缓存机制
java1/**2 * Buffer 源码解析3 * 4 * Buffer是一个有限容量的容器,用于存储特定基本类型的数据5 */6publicabstractclass Buffer {78 // Buffer属性9privatelong address; // 内存地址10privateint capacity; // 容量11privateint limit; // 限制12privateint position; // 位置13privateint mark; // 标记1415 // 构造方法16Buffer(int mark, int pos, int lim, int cap, long addr) {17if (cap < 0)18thrownewIllegalArgumentException("Negative capacity: " + cap);19this.capacity = cap;20limit(lim);21position(pos);22if (mark >= 0) {23if (mark > pos)24thrownewIllegalArgumentException("mark > position: ("25 + mark + " > " + pos + ")");26this.mark = mark;27 } else {28this.mark = -1;29 }30this.address = addr;31 }3233 /**34 * 获取位置35 */36publicfinalintposition() {37return position;38 }3940 /**41 * 设置位置42 */43publicfinal Buffer position(int newPosition) {44if (newPosition > limit || newPosition < 0)45thrownewIllegalArgumentException();46 position = newPosition;47if (mark > position) mark = -1;48returnthis;49 }5051 /**52 * 获取限制53 */54publicfinalintlimit() {55return limit;56 }5758 /**59 * 设置限制60 */61publicfinal Buffer limit(int newLimit) {62if (newLimit > capacity || newLimit < 0)63thrownewIllegalArgumentException();64 limit = newLimit;65if (position > limit) position = limit;66if (mark > limit) mark = -1;67returnthis;68 }6970 /**71 * 切换到读模式72 */73publicfinal Buffer flip() {74 limit = position;75 position = 0;76 mark = -1;77returnthis;78 }7980 /**81 * 切换到写模式82 */83publicfinal Buffer clear() {84 position = 0;85 limit = capacity;86 mark = -1;87returnthis;88 }8990 /**91 * 标记当前位置92 */93publicfinal Buffer mark() {94 mark = position;95returnthis;96 }9798 /**99 * 回到标记位置100 */101publicfinal Buffer reset() {102int m = mark;103if (m < 0)104thrownewInvalidMarkException();105 position = m;106returnthis;107 }108}二、NIO核心源码
2.1 Channel通道
java1/**2 * Channel 接口 - NIO核心3 * 4 * Channel表示一个打开的连接,可以进行IO操作5 */6publicinterface Channel extends Closeable {78 // 判断通道是否打开9publicbooleanisOpen();1011 // 关闭通道12publicvoidclose() throws IOException;13}1415/**16 * FileChannel 实现17 */18publicabstractclass FileChannel extends AbstractInterruptibleChannel19implements SeekableByteChannel, GatheringByteChannel, ScatteringByteChannel {2021 // 成员变量22protectedfinal FileDescriptor fd;23privatefinalString path;2425 /**26 * 读取数据到Buffer27 */28publicintread(ByteBuffer dst) throws IOException {29synchronized (this) {30ensureOpen();31returnreadInternal(dst, -1);32 }33 }3435privateintreadInternal(ByteBuffer dst, long position) throws IOException {36 // 省略实现细节...37int n = 0;38try {39begin();40 n = -1;41 // 调用native方法42 n = readInternal0(position, dst, dst.position(), dst.limit() - dst.position());43if (n > 0) {44 dst.position(dst.position() + n);45 }46 } finally {47end(n > 0);48 }49return n;50 }5152 // native方法53privatenativeintreadInternal0(long position, ByteBuffer bb, int offset, int length) throws IOException;5455 /**56 * 写入数据57 */58publicintwrite(ByteBuffer src) throws IOException {59synchronized (this) {60ensureOpen();61returnwriteInternal(src, -1);62 }63 }6465 /**66 * 传输数据到另一个Channel67 */68publiclongtransferTo(long position, long count, WritableByteChannel target)69throws IOException70 {71if (!target.isOpen())72thrownewClosedChannelException();73if (!isOpen())74thrownewClosedChannelException();75if (!readable)76thrownewNonReadableChannelException();7778long n = -1;79try {80begin();81 n = transferTo0(position, count, target);82 } finally {83end(n > 0);84 }85return n;86 }8788 // native方法89privatenativelongtransferTo0(long position, long count, WritableByteChannel target)90throws IOException;91}2.2 Selector选择器
java1/**2 * Selector 源码解析3 * 4 * Selector是IO多路复用的核心,用于监控多个Channel的状态5 */6publicabstractclass Selector implements Closeable {78 // 成员变量9protectedSet<SelectionKey> keys; // 注册的key10privateSet<SelectionKey> selectedKeys; // 已选择的key11privatevolatileint wakeupSocket; // 唤醒标记1213 /**14 * 打开Selector15 */16publicstatic Selector open() throws IOException {17return SelectorProvider.provider().openSelector();18 }1920 /**21 * 选择就绪的Channel22 */23publicintselect() throws IOException {24returnselect(0);25 }2627 /**28 * 带超时的选择29 */30publicintselect(long timeout) throws IOException {31if (timeout < 0)32thrownewIllegalArgumentException("Negative timeout");33returnlockAndDoSelect(timeout == 0 ? -1 : timeout);34 }3536privateintlockAndDoSelect(long timeout) throws IOException {37synchronized (this) {38ensureOpen();39returndoSelect(timeout);40 }41 }4243 /**44 * 实际的选择操作 - abstract方法45 */46protectedabstractintdoSelect(long timeout) throws IOException;4748 /**49 * 立即返回的选择50 */51publicintselectNow() throws IOException {52returnselect(0);53 }5455 /**56 * 唤醒Selector57 */58public Selector wakeup() {59 // 唤醒阻塞的select60 wakeupSocket = 1;61returnthis;62 }63}6465/**66 * SelectionKey67 * 68 * 表示一个Channel注册到Selector上的状态69 */70publicabstractclass SelectionKey {7172 // 就绪操作73publicstaticfinalint OP_READ = 1 << 0; // 0001 = 174publicstaticfinalint OP_WRITE = 1 << 2; // 0100 = 475publicstaticfinalint OP_CONNECT = 1 << 3; // 1000 = 876publicstaticfinalint OP_ACCEPT = 1 << 4; // 0001 0000 = 167778 // 成员变量79privatevolatileint interestOps; // 关注操作80privateint readyOps; // 就绪操作8182 /**83 * 获取Channel84 */85publicabstract SelectableChannel channel();8687 /**88 * 获取Selector89 */90publicabstract Selector selector();9192 /**93 * 设置关注操作94 */95public SelectionKey interestOps(int ops) {96checkValid();97returninterestOps0(ops);98 }99100 /**101 * 获取就绪操作102 */103publicintreadyOps() {104checkValid();105return readyOps;106 }107108 /**109 * 判断是否可读110 */111publicfinalbooleanisReadable() {112return (readyOps() & OP_READ) != 0;113 }114115 /**116 * 判断是否可写117 */118publicfinalbooleanisWritable() {119return (readyOps() & OP_WRITE) != 0;120 }121}三、IO模型对比
3.1 BIO vs NIO vs AIO
java1/**2 * IO模型对比示例3 */45// BIO - 阻塞IO6publicclass BIOServer {7publicvoidhandle() throws IOException {8 ServerSocket serverSocket = newServerSocket(8080);9while (true) {10 // 阻塞:等待连接11 Socket client = serverSocket.accept();12 // 为每个客户端创建新线程13newThread(() -> {14try {15 // 阻塞:等待数据16 BufferedReader reader = newBufferedReader(17newInputStreamReader(client.getInputStream()));18String line;19while ((line = reader.readLine()) != null) {20 // 处理数据21 }22 } catch (IOException e) {23 e.printStackTrace();24 }25 }).start();26 }27 }28}2930// NIO - 非阻塞IO31publicclass NIOServer {32publicvoidhandle() throws IOException {33 Selector selector = Selector.open();34 ServerSocketChannel server = ServerSocketChannel.open();35 server.configureBlocking(false);36 server.register(selector, SelectionKey.OP_ACCEPT);3738while (true) {39 // 阻塞:等待事件40 selector.select();4142Set<SelectionKey> keys = selector.selectedKeys();43for (SelectionKey key : keys) {44if (key.isAcceptable()) {45 // 接受连接46 ServerSocketChannel ssc = (ServerSocketChannel) key.channel();47 SocketChannel sc = ssc.accept();48 sc.configureBlocking(false);49 sc.register(selector, SelectionKey.OP_READ);50 } elseif (key.isReadable()) {51 // 读取数据52 SocketChannel sc = (SocketChannel) key.channel();53 ByteBuffer buffer = ByteBuffer.allocate(1024);54 sc.read(buffer);55 }56 }57 }58 }59}3.2 零拷贝实现
java1/**2 * 零拷贝技术3 * 4 * 传统IO:4次拷贝5 * 1. 磁盘 -> 内核缓冲区6 * 2. 内核缓冲区 -> 用户空间7 * 3. 用户空间 -> Socket缓冲区8 * 4. Socket缓冲区 -> 网卡9 * 10 * 零拷贝:2次拷贝11 * 1. 磁盘 -> 内核缓冲区12 * 2. 内核缓冲区 -> 网卡13 */14publicclass ZeroCopyDemo {1516 /**17 * FileChannel.transferTo 实现18 */19publicvoidtransferTo(FileInputStream source, FileOutputStream dest) 20throws IOException {2122 FileChannel fromChannel = source.getChannel();23 FileChannel toChannel = dest.getChannel();2425 // 使用零拷贝26long position = 0;27long size = fromChannel.size();2829 // 循环传输大文件30while (position < size) {31long transferred = fromChannel.transferTo(32 position, 33 size - position, 34 toChannel35 );36 position += transferred;37 }38 }3940 /**41 * sendfile系统调用42 */43publicvoidsendfile() throws IOException {44 // Linux sendfile(int out_fd, int in_fd, off_t *offset, size_t count)45 // 在内核空间直接完成数据传输46 }47}四、实战优化
4.1 Buffer池化
java1/**2 * Buffer池化实现3 */4publicclass BufferPool {56 // 池大小7privatestaticfinalint POOL_SIZE = 64;89 // ByteBuffer池10privatestaticfinal ConcurrentLinkedQueue<ByteBuffer> POOL = 11new ConcurrentLinkedQueue<>();1213static {14 // 初始化池15for (int i = 0; i < POOL_SIZE; i++) {16 POOL.offer(ByteBuffer.allocateDirect(8192));17 }18 }1920 /**21 * 获取Buffer22 */23publicstatic ByteBuffer acquire() {24 ByteBuffer buffer = POOL.poll();25if (buffer == null) {26 // 池为空,创建新Buffer27return ByteBuffer.allocateDirect(8192);28 }29 // 清除Buffer30 buffer.clear();31return buffer;32 }3334 /**35 * 归还Buffer36 */37publicstaticvoidrelease(ByteBuffer buffer) {38if (buffer != null && POOL.size() < POOL_SIZE) {39 buffer.clear();40 POOL.offer(buffer);41 }42 }43}4445/**46 * 使用示例47 */48publicclass NIOServer {49publicvoidhandle() {50 ByteBuffer buffer = BufferPool.acquire();51try {52 channel.read(buffer);53 // 处理数据54 } finally {55 BufferPool.release(buffer);56 }57 }58}4.2 分散读取与聚合写入
java1/**2 * Scatter/Gather IO3 * 4 * 分散读取:将数据读取到多个Buffer5 * 聚合写入:将多个Buffer的数据写入到同一位置6 */7publicclass ScatterGatherDemo {89 /**10 * 分散读取11 */12publicvoidscatterRead(FileChannel channel) throws IOException {13 // 创建多个Buffer14 ByteBuffer header = ByteBuffer.allocate(128);15 ByteBuffer body = ByteBuffer.allocate(1024);1617 // 分散读取18 ByteBuffer[] buffers = { header, body };19long bytesRead = channel.read(buffers);2021 // 处理数据22 header.flip();23 body.flip();2425while (header.hasRemaining()) {26 System.out.print((char) header.get());27 }28 }2930 /**31 * 聚合写入32 */33publicvoidgatherWrite(FileChannel channel) throws IOException {34 ByteBuffer header = ByteBuffer.allocate(128);35 ByteBuffer body = ByteBuffer.allocate(1024);3637 // 写入数据38 header.put("Header Data".getBytes());39 body.put("Body Data".getBytes());4041 // 聚合写入42 ByteBuffer[] buffers = { header, body };43 channel.write(buffers);44 }45}总结
1┌─────────────────────────────────────────────────────────────────────┐2│ Java IO与NIO要点总结 │3├─────────────────────────────────────────────────────────────────────┤4│ │5│ 📖 BIO源码 │6│ └── InputStream / OutputStream / FileReader │7│ │8│ 🔄 NIO核心 │9│ └── Channel / Buffer / Selector │10│ │11│ ⚡ IO模型对比 │12│ └── BIO / NIO / AIO │13│ │14│ 🚀 零拷贝 │15│ └── transferTo / sendfile │16│ │17│ 💡 性能优化 │18│ └── Buffer池 / Scatter/Gather │19│ │20└─────────────────────────────────────────────────────────────────────┘🚀 加入「军军程序学堂」,掌握更多Java IO与NIO实战技巧!
夜雨聆风