传音Java面试被问:HTTP/2的多路复用和头部压缩实现

一、核心架构与协议帧

1. HTTP/2 帧结构

java

复制

下载

复制代码
/**
 * HTTP/2 帧基础结构
 * 所有帧都遵循9字节的帧头格式
 */
public class Http2Frame {
    // RFC 7540 定义的帧结构
    // +-----------------------------------------------+
    // |                 Length (24)                   |
    // +---------------+---------------+---------------+
    // |   Type (8)    |   Flags (8)   |
    // +-+-------------+---------------+-------------------------------+
    // |R|                 Stream Identifier (31)                      |
    // +-+-------------------------------------------------------------+
    // |                   Frame Payload (0...)                      ...
    // +---------------------------------------------------------------+
    
    /**
     * 帧类型枚举
     */
    public enum FrameType {
        DATA(0x0),           // 数据帧
        HEADERS(0x1),        // 头部帧
        PRIORITY(0x2),       // 优先级帧
        RST_STREAM(0x3),     // 流终止帧
        SETTINGS(0x4),       // 设置帧
        PUSH_PROMISE(0x5),   // 推送承诺帧
        PING(0x6),          // PING帧
        GOAWAY(0x7),        // 连接关闭帧
        WINDOW_UPDATE(0x8), // 窗口更新帧
        CONTINUATION(0x9);  // 延续帧
        
        private final int value;
        FrameType(int value) { this.value = value; }
        public int getValue() { return value; }
    }
    
    /**
     * 帧头
     */
    public static class FrameHeader {
        private int length;           // 24位,负载长度
        private FrameType type;       // 8位,帧类型
        private byte flags;           // 8位,标志位
        private int streamId;         // 31位,流标识符(0表示连接控制帧)
        
        // 标志位常量
        public static final byte END_STREAM = 0x1;
        public static final byte END_HEADERS = 0x4;
        public static final byte PADDED = 0x8;
        public static final byte PRIORITY = 0x20;
        
        public byte[] toBytes() {
            ByteBuffer buffer = ByteBuffer.allocate(9);
            buffer.put((byte) (length >>> 16));  // 长度高位
            buffer.put((byte) (length >>> 8));   // 长度中位
            buffer.put((byte) length);           // 长度低位
            buffer.put((byte) type.getValue());  // 类型
            buffer.put(flags);                   // 标志位
            buffer.putInt(streamId & 0x7FFFFFFF); // 流ID(最高位保留)
            return buffer.array();
        }
    }
}

2. 多路复用核心:流(Stream)

java

复制

下载

复制代码
/**
 * HTTP/2 流(Stream)实现
 * 每个流代表一个独立的请求-响应交换
 */
public class Http2Stream implements Comparable<Http2Stream> {
    private final int streamId;
    private volatile StreamState state;
    private final PriorityTreeNode priorityNode;
    private final ConcurrentLinkedDeque<Http2Frame> sendQueue;
    private final ConcurrentLinkedDeque<Http2Frame> receiveQueue;
    
    // 流状态机
    public enum StreamState {
        IDLE,               // 空闲
        RESERVED_LOCAL,     // 本地保留
        RESERVED_REMOTE,    // 远程保留
        OPEN,               // 打开
        HALF_CLOSED_LOCAL,  // 本地半关闭
        HALF_CLOSED_REMOTE, // 远程半关闭
        CLOSED              // 关闭
    }
    
    /**
     * 优先级树节点
     * HTTP/2使用优先级树管理流的发送顺序
     */
    public static class PriorityTreeNode {
        private final Http2Stream stream;
        private int weight;                    // 权重 1-256
        private boolean exclusive;             // 独占标志
        private PriorityTreeNode parent;       // 父节点
        private List<PriorityTreeNode> children; // 子节点
        
        public PriorityTreeNode(Http2Stream stream, int weight) {
            this.stream = stream;
            this.weight = Math.max(1, Math.min(256, weight));
            this.children = new ArrayList<>();
        }
        
        /**
         * 插入新的优先级节点
         * @param newChild 新节点
         * @param exclusive 是否独占(插入为唯一直接子节点)
         */
        public void insertChild(PriorityTreeNode newChild, boolean exclusive) {
            if (exclusive) {
                // 独占模式:新节点成为父节点的唯一直接子节点
                // 原来的子节点成为新节点的子节点
                for (PriorityTreeNode child : children) {
                    newChild.addChild(child);
                }
                children.clear();
                children.add(newChild);
                newChild.parent = this;
            } else {
                children.add(newChild);
                newChild.parent = this;
            }
        }
    }
    
    /**
     * 流状态转换(根据RFC 7540 5.1节)
     */
    public boolean transition(StreamState newState, FrameType frameType) {
        synchronized (this) {
            boolean valid = isValidTransition(state, newState, frameType);
            if (valid) {
                state = newState;
                log.debug("Stream {}: {} -> {}", streamId, state, newState);
            }
            return valid;
        }
    }
    
    private boolean isValidTransition(StreamState current, StreamState next, FrameType frame) {
        // 实现RFC 7540 5.1节的状态转换表
        switch (current) {
            case IDLE:
                return (frame == FrameType.HEADERS || frame == FrameType.PUSH_PROMISE) &&
                       (next == StreamState.OPEN || next == StreamState.RESERVED_LOCAL || 
                        next == StreamState.RESERVED_REMOTE);
            case OPEN:
                return (frame == FrameType.RST_STREAM && next == StreamState.CLOSED) ||
                       (frame == FrameType.HEADERS && next == StreamState.HALF_CLOSED_LOCAL) ||
                       ((frame == FrameType.DATA || frame == FrameType.HEADERS) && 
                        next == StreamState.HALF_CLOSED_REMOTE);
            // ... 其他状态转换
            default:
                return false;
        }
    }
    
    @Override
    public int compareTo(Http2Stream other) {
        // 基于优先级的比较
        return Integer.compare(
            this.priorityNode.weight * getDepth(this.priorityNode),
            other.priorityNode.weight * getDepth(other.priorityNode)
        );
    }
    
    private int getDepth(PriorityTreeNode node) {
        int depth = 0;
        while (node.parent != null) {
            depth++;
            node = node.parent;
        }
        return depth;
    }
}

二、多路复用(Multiplexing)实现

1. 连接管理器(Connection Handler)

java

复制

下载

复制代码
/**
 * HTTP/2 连接管理器
 * 管理所有流的多路复用
 */
public class Http2ConnectionHandler {
    // 连接级设置
    private final Http2Settings settings;
    
    // 流管理
    private final AtomicInteger nextStreamId = new AtomicInteger(1); // 客户端从奇数开始
    private final ConcurrentMap<Integer, Http2Stream> streams = new ConcurrentHashMap<>();
    private final PriorityTree priorityTree = new PriorityTree();
    
    // 流量控制
    private final FlowController flowController;
    
    // 发送队列和调度器
    private final PriorityBlockingQueue<Http2Frame> sendQueue;
    private final FrameScheduler frameScheduler;
    
    /**
     * 初始化HTTP/2连接
     */
    public void initializeConnection(SocketChannel channel) throws IOException {
        // 1. 发送连接前言(Connection Preface)
        sendConnectionPreface(channel);
        
        // 2. 交换SETTINGS帧
        exchangeSettings(channel);
        
        // 3. 启动帧调度器
        frameScheduler.start();
        
        log.info("HTTP/2连接初始化完成");
    }
    
    /**
     * 发送连接前言
     * 必须是"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"
     */
    private void sendConnectionPreface(SocketChannel channel) throws IOException {
        String preface = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
        ByteBuffer buffer = ByteBuffer.wrap(preface.getBytes(StandardCharsets.UTF_8));
        channel.write(buffer);
    }
    
    /**
     * 创建新流
     */
    public Http2Stream createStream(int dependencyId, int weight, boolean exclusive) {
        int streamId = nextStreamId.getAndAdd(2); // 客户端流ID为奇数
        
        Http2Stream stream = new Http2Stream(streamId);
        streams.put(streamId, stream);
        
        // 设置优先级
        if (dependencyId == 0) {
            priorityTree.addRootStream(stream, weight);
        } else {
            Http2Stream parentStream = streams.get(dependencyId);
            if (parentStream != null) {
                priorityTree.insertStream(stream, parentStream, weight, exclusive);
            }
        }
        
        log.debug("创建新流: {}", streamId);
        return stream;
    }
    
    /**
     * 接收并处理帧
     */
    public void handleFrame(Http2Frame frame) {
        int streamId = frame.getStreamId();
        
        if (streamId == 0) {
            // 连接级帧
            handleConnectionFrame(frame);
        } else {
            // 流级帧
            Http2Stream stream = streams.get(streamId);
            if (stream != null) {
                handleStreamFrame(stream, frame);
            } else if (streamId >= nextStreamId.get()) {
                // 未知流,发送RST_STREAM
                sendRstStream(streamId, ErrorCode.PROTOCOL_ERROR);
            }
        }
    }
    
    /**
     * 帧调度器 - 基于优先级发送帧
     */
    private class FrameScheduler extends Thread {
        private volatile boolean running = true;
        
        @Override
        public void run() {
            while (running) {
                try {
                    // 1. 基于优先级选择要发送的流
                    Http2Stream stream = selectNextStream();
                    
                    if (stream != null) {
                        // 2. 从流中获取待发送的帧
                        Http2Frame frame = stream.pollSendFrame();
                        
                        if (frame != null) {
                            // 3. 应用流量控制
                            if (flowController.canSend(stream, frame)) {
                                // 4. 发送帧
                                sendFrame(frame);
                                
                                // 5. 更新流量控制窗口
                                flowController.updateWindow(stream, frame);
                            }
                        }
                    } else {
                        Thread.sleep(1); // 避免忙等待
                    }
                    
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        }
        
        /**
         * 选择下一个要发送的流(基于优先级和权重)
         */
        private Http2Stream selectNextStream() {
            return priorityTree.getNextStream();
        }
        
        public void shutdown() {
            running = false;
            interrupt();
        }
    }
    
    /**
     * 优先级树实现
     */
    private class PriorityTree {
        private final Http2Stream rootStream; // 虚拟根流(stream 0)
        private final Map<Integer, PriorityTreeNode> nodeMap;
        
        public PriorityTree() {
            this.rootStream = new Http2Stream(0);
            this.nodeMap = new ConcurrentHashMap<>();
        }
        
        /**
         * 获取下一个要处理的流(加权轮询)
         */
        public Http2Stream getNextStream() {
            List<Http2Stream> candidates = new ArrayList<>();
            collectReadyStreams(rootStream, candidates);
            
            if (candidates.isEmpty()) {
                return null;
            }
            
            // 加权随机选择
            return weightedRandomSelect(candidates);
        }
        
        private void collectReadyStreams(Http2Stream stream, List<Http2Stream> result) {
            if (stream.hasDataToSend()) {
                result.add(stream);
            }
            
            for (PriorityTreeNode child : stream.getPriorityNode().getChildren()) {
                collectReadyStreams(child.getStream(), result);
            }
        }
        
        private Http2Stream weightedRandomSelect(List<Http2Stream> streams) {
            int totalWeight = streams.stream()
                .mapToInt(s -> s.getPriorityNode().getWeight())
                .sum();
            
            int random = ThreadLocalRandom.current().nextInt(totalWeight);
            int cumulative = 0;
            
            for (Http2Stream stream : streams) {
                cumulative += stream.getPriorityNode().getWeight();
                if (random < cumulative) {
                    return stream;
                }
            }
            
            return streams.get(0);
        }
    }
}

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

2. 流量控制(Flow Control)

java

复制

下载

复制代码
/**
 * HTTP/2 流量控制器
 * 实现RFC 7540 6.9节的流量控制机制
 */
public class FlowController {
    // 连接级窗口大小(默认65535字节)
    private volatile int connectionWindowSize = 65535;
    private final Object connectionWindowLock = new Object();
    
    // 流级窗口大小
    private final ConcurrentMap<Integer, Integer> streamWindows = new ConcurrentHashMap<>();
    
    // 初始窗口大小(SETTINGS_INITIAL_WINDOW_SIZE)
    private volatile int initialWindowSize = 65535;
    
    /**
     * 检查是否可以发送数据
     */
    public boolean canSend(Http2Stream stream, Http2Frame frame) {
        if (frame.getType() != FrameType.DATA) {
            return true; // 非DATA帧不受流量控制
        }
        
        int dataLength = frame.getPayloadLength();
        int streamId = stream.getStreamId();
        
        synchronized (connectionWindowLock) {
            // 检查连接级窗口
            if (dataLength > connectionWindowSize) {
                return false;
            }
            
            // 检查流级窗口
            int streamWindow = streamWindows.getOrDefault(streamId, initialWindowSize);
            if (dataLength > streamWindow) {
                return false;
            }
            
            return true;
        }
    }
    
    /**
     * 更新窗口大小
     */
    public void updateWindow(Http2Stream stream, Http2Frame frame) {
        if (frame.getType() != FrameType.DATA) {
            return;
        }
        
        int dataLength = frame.getPayloadLength();
        int streamId = stream.getStreamId();
        
        synchronized (connectionWindowLock) {
            // 更新连接级窗口
            connectionWindowSize -= dataLength;
            
            // 更新流级窗口
            streamWindows.compute(streamId, (k, v) -> {
                int current = (v != null) ? v : initialWindowSize;
                return current - dataLength;
            });
            
            // 如果窗口过小,发送WINDOW_UPDATE帧
            if (connectionWindowSize < initialWindowSize / 2) {
                sendWindowUpdate(0, initialWindowSize - connectionWindowSize);
            }
            
            int streamWindow = streamWindows.get(streamId);
            if (streamWindow < initialWindowSize / 2) {
                sendWindowUpdate(streamId, initialWindowSize - streamWindow);
            }
        }
    }
    
    /**
     * 处理WINDOW_UPDATE帧
     */
    public void handleWindowUpdate(int streamId, int windowSizeIncrement) {
        synchronized (connectionWindowLock) {
            if (streamId == 0) {
                // 连接级窗口更新
                connectionWindowSize += windowSizeIncrement;
                log.debug("连接窗口增加 {} 到 {}", windowSizeIncrement, connectionWindowSize);
            } else {
                // 流级窗口更新
                streamWindows.compute(streamId, (k, v) -> {
                    int current = (v != null) ? v : initialWindowSize;
                    int newSize = current + windowSizeIncrement;
                    
                    // 防止窗口溢出(RFC 7540 6.9.1)
                    if (newSize > 0x7FFFFFFF) {
                        throw new Http2Exception(ErrorCode.FLOW_CONTROL_ERROR,
                            "窗口大小溢出: " + newSize);
                    }
                    
                    return newSize;
                });
                log.debug("流 {} 窗口增加 {} 到 {}", streamId, windowSizeIncrement, 
                         streamWindows.get(streamId));
            }
        }
    }
}

三、头部压缩(HPACK)实现

1. HPACK编码器/解码器

java

复制

下载

复制代码
/**
 * HPACK头部压缩实现(RFC 7541)
 */
public class HpackCodec {
    // 静态表(RFC 7541 附录A)
    private static final List<HeaderField> STATIC_TABLE = createStaticTable();
    
    // 动态表(先进先出)
    private final EvictingRingBuffer<HeaderField> dynamicTable;
    
    // 动态表大小限制
    private int dynamicTableSize = 4096; // 默认4KB
    private int maxDynamicTableSize = 4096;
    
    /**
     * HPACK头部字段
     */
    public static class HeaderField {
        private final String name;
        private final String value;
        private final int size; // 计算大小:name.length + value.length + 32
        
        public HeaderField(String name, String value) {
            this.name = name;
            this.value = value;
            this.size = name.length() + value.length() + 32;
        }
        
        public int getSize() { return size; }
    }
    
    /**
     * HPACK编码器
     */
    public static class Encoder {
        private final HpackCodec codec;
        
        public Encoder(HpackCodec codec) {
            this.codec = codec;
        }
        
        /**
         * 编码头部列表
         */
        public byte[] encodeHeaders(List<HeaderField> headers) throws IOException {
            ByteArrayOutputStream output = new ByteArrayOutputStream();
            
            for (HeaderField header : headers) {
                // 1. 尝试在静态表中查找完全匹配
                int staticIndex = findInStaticTable(header);
                if (staticIndex > 0) {
                    encodeIndexedField(staticIndex, output);
                    continue;
                }
                
                // 2. 尝试在动态表中查找完全匹配
                int dynamicIndex = codec.findInDynamicTable(header);
                if (dynamicIndex > 0) {
                    encodeIndexedField(dynamicIndex + STATIC_TABLE.size(), output);
                    continue;
                }
                
                // 3. 尝试查找名称匹配(使用索引的Literal表示)
                int nameIndex = findNameInTables(header.name);
                if (nameIndex > 0) {
                    encodeLiteralWithIndexing(nameIndex, header.value, output);
                } else {
                    // 4. 全新的头部字段
                    encodeLiteralNewName(header.name, header.value, output);
                }
                
                // 5. 添加到动态表
                codec.addToDynamicTable(header);
            }
            
            return output.toByteArray();
        }
        
        /**
         * 编码索引字段(6.1节)
         * 格式:1xxxxxxx
         */
        private void encodeIndexedField(int index, OutputStream out) throws IOException {
            out.write(0x80 | (index & 0x7F));
        }
        
        /**
         * 编码Literal头部字段,带索引(6.2.1节)
         * 格式:01xxxxxx
         */
        private void encodeLiteralWithIndexing(int nameIndex, String value, 
                                              OutputStream out) throws IOException {
            // 前两位01表示Literal with Incremental Indexing
            out.write(0x40 | (nameIndex & 0x3F));
            encodeString(value, out);
        }
        
        /**
         * 编码Literal头部字段,新名称(6.2.2节)
         */
        private void encodeLiteralNewName(String name, String value, 
                                         OutputStream out) throws IOException {
            // 前两位01,名称索引为0
            out.write(0x40);
            encodeString(name, out);
            encodeString(value, out);
        }
        
        /**
         * 编码字符串(5.2节)
         */
        private void encodeString(String str, OutputStream out) throws IOException {
            byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
            
            // Huffman编码
            byte[] huffmanEncoded = Huffman.encode(bytes);
            
            if (huffmanEncoded.length < bytes.length) {
                // 使用Huffman编码
                out.write(0x80 | (huffmanEncoded.length & 0x7F));
                out.write(huffmanEncoded);
            } else {
                // 不使用Huffman编码
                out.write(bytes.length & 0x7F);
                out.write(bytes);
            }
        }
    }
    
    /**
     * HPACK解码器
     */
    public static class Decoder {
        private final HpackCodec codec;
        
        public Decoder(HpackCodec codec) {
            this.codec = codec;
        }
        
        /**
         * 解码头部块
         */
        public List<HeaderField> decodeHeaders(byte[] data) throws IOException {
            ByteArrayInputStream input = new ByteArrayInputStream(data);
            List<HeaderField> headers = new ArrayList<>();
            
            while (input.available() > 0) {
                int firstByte = input.read() & 0xFF;
                
                if ((firstByte & 0x80) != 0) {
                    // 索引头部字段(6.1节)
                    int index = decodeInteger(firstByte & 0x7F, input);
                    HeaderField header = codec.getHeaderField(index);
                    headers.add(header);
                    
                } else if ((firstByte & 0xC0) == 0x40) {
                    // Literal with Incremental Indexing(6.2.1节)
                    int index = decodeInteger(firstByte & 0x3F, input);
                    String name = (index == 0) ? decodeString(input) : 
                                 codec.getHeaderField(index).getName();
                    String value = decodeString(input);
                    
                    HeaderField header = new HeaderField(name, value);
                    headers.add(header);
                    codec.addToDynamicTable(header);
                    
                } else if ((firstByte & 0xF0) == 0x00) {
                    // Literal without Indexing(6.2.2节)
                    int index = decodeInteger(firstByte & 0x0F, input);
                    String name = (index == 0) ? decodeString(input) : 
                                 codec.getHeaderField(index).getName();
                    String value = decodeString(input);
                    
                    headers.add(new HeaderField(name, value));
                    
                } else if ((firstByte & 0xF0) == 0x10) {
                    // Literal never Indexed(6.2.3节)
                    int index = decodeInteger(firstByte & 0x0F, input);
                    String name = (index == 0) ? decodeString(input) : 
                                 codec.getHeaderField(index).getName();
                    String value = decodeString(input);
                    
                    headers.add(new HeaderField(name, value));
                } else if (firstByte == 0x20) {
                    // 动态表大小更新(6.3节)
                    int newSize = decodeInteger(0x1F, input);
                    codec.setDynamicTableSize(newSize);
                }
            }
            
            return headers;
        }
        
        /**
         * 解码整数(5.1节)
         */
        private int decodeInteger(int prefixMask, InputStream input) throws IOException {
            int value = prefixMask;
            
            if (value < prefixMask) {
                return value; // 值就在前缀中
            }
            
            int m = 0;
            int b;
            do {
                b = input.read();
                value += (b & 0x7F) << m;
                m += 7;
            } while ((b & 0x80) != 0);
            
            return value;
        }
        
        /**
         * 解码字符串(5.2节)
         */
        private String decodeString(InputStream input) throws IOException {
            int firstByte = input.read();
            boolean huffmanEncoded = (firstByte & 0x80) != 0;
            int length = decodeInteger(firstByte & 0x7F, input);
            
            byte[] data = new byte[length];
            input.read(data);
            
            if (huffmanEncoded) {
                return new String(Huffman.decode(data), StandardCharsets.UTF_8);
            } else {
                return new String(data, StandardCharsets.UTF_8);
            }
        }
    }
    
    /**
     * 创建静态表(RFC 7541 附录A)
     */
    private static List<HeaderField> createStaticTable() {
        List<HeaderField> table = new ArrayList<>();
        table.add(null); // 索引从1开始,所以0位置为空
        
        // 添加RFC 7541附录A定义的61个条目
        table.add(new HeaderField(":authority", ""));
        table.add(new HeaderField(":method", "GET"));
        table.add(new HeaderField(":method", "POST"));
        table.add(new HeaderField(":path", "/"));
        table.add(new HeaderField(":path", "/index.html"));
        table.add(new HeaderField(":scheme", "http"));
        table.add(new HeaderField(":scheme", "https"));
        table.add(new HeaderField(":status", "200"));
        table.add(new HeaderField(":status", "204"));
        table.add(new HeaderField(":status", "206"));
        // ... 继续添加其他58个条目
        
        return Collections.unmodifiableList(table);
    }
    
    /**
     * 在动态表中查找头部字段
     */
    public int findInDynamicTable(HeaderField header) {
        for (int i = 0; i < dynamicTable.size(); i++) {
            HeaderField entry = dynamicTable.get(i);
            if (entry.name.equals(header.name) && entry.value.equals(header.value)) {
                return i + 1; // 动态表索引从静态表之后开始
            }
        }
        return -1;
    }
    
    /**
     * 添加头部字段到动态表
     */
    public synchronized void addToDynamicTable(HeaderField header) {
        int headerSize = header.getSize();
        
        // 确保动态表大小不超过限制
        while (dynamicTableSize + headerSize > maxDynamicTableSize) {
            if (dynamicTable.isEmpty()) {
                break;
            }
            HeaderField removed = dynamicTable.remove();
            dynamicTableSize -= removed.getSize();
        }
        
        if (headerSize <= maxDynamicTableSize) {
            dynamicTable.add(header);
            dynamicTableSize += headerSize;
        }
    }
}

2. Huffman编码实现

java

复制

下载

复制代码
/**
 * HPACK Huffman编码(RFC 7541 附录B)
 */
public class Huffman {
    // Huffman编码表(RFC 7541 附录B)
    private static final HuffmanCode[] HUFFMAN_CODES = createHuffmanTable();
    
    /**
     * Huffman编码条目
     */
    private static class HuffmanCode {
        final int code;     // 编码值
        final int length;   // 编码长度(位)
        
        HuffmanCode(int code, int length) {
            this.code = code;
            this.length = length;
        }
    }
    
    /**
     * 编码字节数组
     */
    public static byte[] encode(byte[] input) {
        BitOutputStream bitStream = new BitOutputStream();
        
        for (byte b : input) {
            int unsignedByte = b & 0xFF;
            HuffmanCode hc = HUFFMAN_CODES[unsignedByte];
            
            // 写入Huffman编码
            bitStream.writeBits(hc.code, hc.length);
        }
        
        // 添加EOS(End of String)标记
        bitStream.writeBits(0x3FFFFFFF, 30);
        
        return bitStream.toByteArray();
    }
    
    /**
     * 解码字节数组
     */
    public static byte[] decode(byte[] encoded) throws IOException {
        BitInputStream bitStream = new BitInputStream(encoded);
        ByteArrayOutputStream output = new ByteArrayOutputStream();
        
        int currentCode = 0;
        int bitCount = 0;
        
        while (true) {
            int bit = bitStream.readBit();
            if (bit == -1) {
                break; // 输入结束
            }
            
            currentCode = (currentCode << 1) | bit;
            bitCount++;
            
            // 查找匹配的Huffman编码
            int symbol = lookupHuffmanCode(currentCode, bitCount);
            if (symbol != -1) {
                if (symbol == 256) { // EOS
                    break;
                }
                output.write(symbol);
                currentCode = 0;
                bitCount = 0;
            }
            
            if (bitCount > 30) {
                throw new IOException("无效的Huffman编码");
            }
        }
        
        // 检查是否有未使用的位(应该是0)
        int remainingBits = bitStream.getRemainingBits();
        if (remainingBits > 7) {
            throw new IOException("填充位过多");
        }
        
        return output.toByteArray();
    }
    
    /**
     * 位输出流
     */
    private static class BitOutputStream {
        private int currentByte = 0;
        private int bitCount = 0;
        private final ByteArrayOutputStream output = new ByteArrayOutputStream();
        
        public void writeBits(int code, int length) {
            for (int i = length - 1; i >= 0; i--) {
                int bit = (code >> i) & 1;
                currentByte = (currentByte << 1) | bit;
                bitCount++;
                
                if (bitCount == 8) {
                    output.write(currentByte);
                    currentByte = 0;
                    bitCount = 0;
                }
            }
        }
        
        public byte[] toByteArray() {
            if (bitCount > 0) {
                // 填充剩余位为1(RFC 7541 5.2节)
                currentByte <<= (8 - bitCount);
                currentByte |= (1 << (8 - bitCount)) - 1;
                output.write(currentByte);
            }
            return output.toByteArray();
        }
    }
    
    /**
     * 位输入流
     */
    private static class BitInputStream {
        private final byte[] data;
        private int byteIndex = 0;
        private int bitIndex = 0;
        
        public BitInputStream(byte[] data) {
            this.data = data;
        }
        
        public int readBit() {
            if (byteIndex >= data.length) {
                return -1;
            }
            
            int bit = (data[byteIndex] >> (7 - bitIndex)) & 1;
            bitIndex++;
            
            if (bitIndex == 8) {
                byteIndex++;
                bitIndex = 0;
            }
            
            return bit;
        }
        
        public int getRemainingBits() {
            return (data.length - byteIndex) * 8 - bitIndex;
        }
    }
}

四、完整HTTP/2客户端实现

java

复制

下载

复制代码
/**
 * 完整的HTTP/2客户端实现
 */
public class Http2Client {
    private final SocketChannel channel;
    private final Http2ConnectionHandler connectionHandler;
    private final HpackCodec hpackCodec;
    private final ExecutorService executor;
    
    // 连接状态
    private volatile boolean connected = false;
    
    public Http2Client(String host, int port) throws IOException {
        this.channel = SocketChannel.open();
        this.channel.connect(new InetSocketAddress(host, port));
        this.channel.configureBlocking(false);
        
        this.hpackCodec = new HpackCodec();
        this.connectionHandler = new Http2ConnectionHandler(hpackCodec);
        this.executor = Executors.newCachedThreadPool();
        
        initializeHttp2Connection();
    }
    
    /**
     * 初始化HTTP/2连接
     */
    private void initializeHttp2Connection() throws IOException {
        // 1. 发送连接前言
        sendConnectionPreface();
        
        // 2. 发送初始SETTINGS帧
        sendInitialSettings();
        
        // 3. 启动接收线程
        startReceiverThread();
        
        connected = true;
    }
    
    /**
     * 发送HTTP/2请求
     */
    public CompletableFuture<Http2Response> sendRequest(Http2Request request) {
        CompletableFuture<Http2Response> future = new CompletableFuture<>();
        
        executor.submit(() -> {
            try {
                // 1. 创建新流
                Http2Stream stream = connectionHandler.createStream(
                    request.getDependencyId(),
                    request.getWeight(),
                    request.isExclusive()
                );
                
                // 2. 编码请求头部
                byte[] encodedHeaders = hpackCodec.getEncoder().encodeHeaders(
                    request.getHeaders()
                );
                
                // 3. 发送HEADERS帧
                Http2Frame headersFrame = new Http2Frame.Builder()
                    .type(FrameType.HEADERS)
                    .streamId(stream.getStreamId())
                    .flags(Http2Frame.FrameHeader.END_HEADERS)
                    .payload(encodedHeaders)
                    .build();
                
                connectionHandler.sendFrame(headersFrame);
                
                // 4. 如果有请求体,发送DATA帧
                if (request.getBody() != null && request.getBody().length > 0) {
                    sendRequestBody(stream, request.getBody());
                }
                
                // 5. 等待响应
                Http2Response response = awaitResponse(stream, request.getTimeout());
                future.complete(response);
                
            } catch (Exception e) {
                future.completeExceptionally(e);
            }
        });
        
        return future;
    }
    
    /**
     * 发送请求体
     */
    private void sendRequestBody(Http2Stream stream, byte[] body) {
        int offset = 0;
        int maxFrameSize = connectionHandler.getMaxFrameSize();
        
        while (offset < body.length) {
            int chunkSize = Math.min(maxFrameSize, body.length - offset);
            byte[] chunk = Arrays.copyOfRange(body, offset, offset + chunkSize);
            
            byte flags = 0;
            if (offset + chunkSize >= body.length) {
                flags = Http2Frame.FrameHeader.END_STREAM;
            }
            
            Http2Frame dataFrame = new Http2Frame.Builder()
                .type(FrameType.DATA)
                .streamId(stream.getStreamId())
                .flags(flags)
                .payload(chunk)
                .build();
            
            connectionHandler.sendFrame(dataFrame);
            offset += chunkSize;
        }
    }
    
    /**
     * 等待响应
     */
    private Http2Response awaitResponse(Http2Stream stream, long timeout) 
            throws InterruptedException, TimeoutException {
        
        long deadline = System.currentTimeMillis() + timeout;
        List<HeaderField> responseHeaders = new ArrayList<>();
        ByteArrayOutputStream responseBody = new ByteArrayOutputStream();
        
        while (System.currentTimeMillis() < deadline) {
            Http2Frame frame = stream.pollReceivedFrame(100, TimeUnit.MILLISECONDS);
            
            if (frame != null) {
                switch (frame.getType()) {
                    case HEADERS:
                        // 解码响应头部
                        List<HeaderField> headers = hpackCodec.getDecoder()
                            .decodeHeaders(frame.getPayload());
                        responseHeaders.addAll(headers);
                        break;
                        
                    case DATA:
                        // 收集响应体
                        responseBody.write(frame.getPayload());
                        
                        if ((frame.getFlags() & Http2Frame.FrameHeader.END_STREAM) != 0) {
                            // 响应结束
                            return buildResponse(responseHeaders, responseBody.toByteArray());
                        }
                        break;
                        
                    case RST_STREAM:
                        throw new IOException("流被重置: " + frame.getPayloadAsInt());
                        
                    default:
                        // 忽略其他帧
                        break;
                }
            }
        }
        
        throw new TimeoutException("等待响应超时");
    }
    
    /**
     * 并行发送多个请求(多路复用演示)
     */
    public List<CompletableFuture<Http2Response>> sendParallelRequests(
            List<Http2Request> requests) {
        
        return requests.stream()
            .map(this::sendRequest)
            .collect(Collectors.toList());
    }
    
    /**
     * 演示多路复用优势
     */
    public void demonstrateMultiplexing() throws Exception {
        List<Http2Request> requests = Arrays.asList(
            createRequest("/api/users", "GET", null),
            createRequest("/api/products", "GET", null),
            createRequest("/api/orders", "POST", "{\"item\": \"book\"}"),
            createRequest("/api/images/1.jpg", "GET", null)
        );
        
        // 设置不同的优先级
        requests.get(0).setPriority(0, 256, true);  // 最高优先级
        requests.get(1).setPriority(0, 128, false);
        requests.get(2).setPriority(1, 64, false);  // 依赖第一个请求
        requests.get(3).setPriority(0, 32, false);  // 最低优先级
        
        long startTime = System.currentTimeMillis();
        
        // 并行发送所有请求
        List<CompletableFuture<Http2Response>> futures = sendParallelRequests(requests);
        
        // 等待所有响应
        CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).get();
        
        long endTime = System.currentTimeMillis();
        System.out.printf("完成 %d 个请求,耗时 %dms\n", 
                         requests.size(), endTime - startTime);
        
        // 与HTTP/1.1对比
        System.out.println("相比HTTP/1.1(需要4个TCP连接或串行),性能提升显著");
    }
    
    private Http2Request createRequest(String path, String method, String body) {
        Http2Request request = new Http2Request();
        
        List<HeaderField> headers = Arrays.asList(
            new HeaderField(":method", method),
            new HeaderField(":path", path),
            new HeaderField(":scheme", "https"),
            new HeaderField(":authority", "api.example.com"),
            new HeaderField("user-agent", "Http2Client/1.0"),
            new HeaderField("accept", "application/json")
        );
        
        request.setHeaders(headers);
        
        if (body != null) {
            request.setBody(body.getBytes(StandardCharsets.UTF_8));
        }
        
        return request;
    }
}

五、性能优化与最佳实践

1. 帧打包优化

java

复制

下载

复制代码
/**
 * 帧打包器 - 优化帧发送
 */
public class FramePacker {
    /**
     * 打包多个小帧为一个TCP数据包(避免Nagle算法影响)
     */
    public List<ByteBuffer> packFrames(List<Http2Frame> frames, int mtu) {
        List<ByteBuffer> packets = new ArrayList<>();
        ByteBuffer currentPacket = ByteBuffer.allocate(mtu);
        
        for (Http2Frame frame : frames) {
            byte[] frameBytes = frame.toBytes();
            
            if (currentPacket.remaining() < frameBytes.length) {
                // 当前包已满,发送并创建新包
                currentPacket.flip();
                packets.add(currentPacket);
                currentPacket = ByteBuffer.allocate(mtu);
            }
            
            currentPacket.put(frameBytes);
        }
        
        if (currentPacket.position() > 0) {
            currentPacket.flip();
            packets.add(currentPacket);
        }
        
        return packets;
    }
    
    /**
     * 头部压缩优化策略
     */
    public static class HeaderCompressionStrategy {
        // 预定义常用头部组合
        private final Map<String, Integer> commonHeaderPatterns = new HashMap<>();
        
        public HeaderCompressionStrategy() {
            // 注册常见API请求头部模式
            registerCommonPattern("api-get", Arrays.asList(
                new HeaderField(":method", "GET"),
                new HeaderField(":scheme", "https"),
                new HeaderField("accept", "application/json"),
                new HeaderField("user-agent", "Http2Client/1.0")
            ));
            
            registerCommonPattern("api-post", Arrays.asList(
                new HeaderField(":method", "POST"),
                new HeaderField(":scheme", "https"),
                new HeaderField("content-type", "application/json"),
                new HeaderField("accept", "application/json")
            ));
        }
        
        private void registerCommonPattern(String name, List<HeaderField> headers) {
            // 计算模式哈希
            String hash = calculateHeaderHash(headers);
            commonHeaderPatterns.put(hash, headers.size());
        }
        
        /**
         * 智能选择编码策略
         */
        public EncodingStrategy chooseStrategy(List<HeaderField> headers) {
            String hash = calculateHeaderHash(headers);
            
            if (commonHeaderPatterns.containsKey(hash)) {
                return EncodingStrategy.PATTERN_BASED;
            }
            
            // 分析头部特征
            long distinctNames = headers.stream()
                .map(HeaderField::getName)
                .distinct()
                .count();
            
            if (distinctNames < headers.size() * 0.3) {
                // 大量重复的头部名称,适合名称索引
                return EncodingStrategy.NAME_INDEXING;
            }
            
            return EncodingStrategy.DEFAULT;
        }
    }
}

2. 连接复用优化

java

复制

下载

复制代码
/**
 * HTTP/2连接池
 */
public class Http2ConnectionPool {
    private final Map<String, List<Http2Client>> connections = new ConcurrentHashMap<>();
    private final int maxConnectionsPerHost;
    private final int maxStreamsPerConnection;
    
    /**
     * 获取连接(智能选择)
     */
    public Http2Client acquireConnection(String host, int port) {
        String key = host + ":" + port;
        List<Http2Client> pool = connections.computeIfAbsent(key, k -> new ArrayList<>());
        
        synchronized (pool) {
            // 1. 查找可用的连接
            for (Http2Client client : pool) {
                if (client.isConnected() && 
                    client.getActiveStreams() < maxStreamsPerConnection) {
                    return client;
                }
            }
            
            // 2. 创建新连接
            if (pool.size() < maxConnectionsPerHost) {
                try {
                    Http2Client newClient = new Http2Client(host, port);
                    pool.add(newClient);
                    return newClient;
                } catch (IOException e) {
                    throw new RuntimeException("创建连接失败", e);
                }
            }
            
            // 3. 等待连接可用
            return waitForAvailableConnection(pool);
        }
    }
    
    /**
     * 连接健康检查
     */
    private class HealthChecker extends Thread {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()) {
                for (List<Http2Client> pool : connections.values()) {
                    for (Http2Client client : pool) {
                        if (!isConnectionHealthy(client)) {
                            // 移除不健康的连接
                            pool.remove(client);
                            try {
                                client.close();
                            } catch (IOException e) {
                                // 忽略关闭异常
                            }
                        }
                    }
                }
                
                try {
                    Thread.sleep(30000); // 30秒检查一次
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }
        
        private boolean isConnectionHealthy(Http2Client client) {
            try {
                // 发送PING帧检查连接
                return client.ping(1000); // 1秒超时
            } catch (Exception e) {
                return false;
            }
        }
    }
}

六、监控与调试

1. 性能监控

java

复制

下载

复制代码
/**
 * HTTP/2连接监控器
 */
public class Http2Monitor {
    private final MeterRegistry meterRegistry;
    
    // 关键性能指标
    private final Timer requestTimer;
    private final DistributionSummary frameSizeDistribution;
    private final Gauge activeStreamsGauge;
    private final Counter compressionRatioCounter;
    
    public Http2Monitor() {
        this.meterRegistry = new SimpleMeterRegistry();
        
        this.requestTimer = Timer.builder("http2.request.duration")
            .publishPercentiles(0.5, 0.95, 0.99)
            .register(meterRegistry);
        
        this.frameSizeDistribution = DistributionSummary
            .builder("http2.frame.size")
            .baseUnit("bytes")
            .register(meterRegistry);
        
        this.activeStreamsGauge = Gauge.builder("http2.streams.active", 
                () -> getActiveStreamsCount())
            .register(meterRegistry);
    }
    
    /**
     * 记录请求性能
     */
    public void recordRequest(long duration, int streamId, boolean success) {
        requestTimer.record(duration, TimeUnit.MILLISECONDS);
        
        Tags tags = Tags.of(
            "stream", String.valueOf(streamId),
            "success", String.valueOf(success)
        );
        meterRegistry.counter("http2.requests.total", tags).increment();
    }
    
    /**
     * 记录头部压缩效果
     */
    public void recordHeaderCompression(int originalSize, int compressedSize) {
        double ratio = (double) compressedSize / originalSize;
        meterRegistry.gauge("http2.header.compression.ratio", ratio);
        
        if (ratio < 0.5) {
            meterRegistry.counter("http2.header.compression.excellent").increment();
        }
    }
    
    /**
     * 生成性能报告
     */
    public PerformanceReport generateReport() {
        PerformanceReport report = new PerformanceReport();
        
        // 收集关键指标
        report.setAverageRequestLatency(requestTimer.mean(TimeUnit.MILLISECONDS));
        report.setP95Latency(requestTimer.percentile(0.95, TimeUnit.MILLISECONDS));
        report.setActiveStreams(activeStreamsGauge.value());
        
        // 计算多路复用效率
        double multiplexingEfficiency = calculateMultiplexingEfficiency();
        report.setMultiplexingEfficiency(multiplexingEfficiency);
        
        return report;
    }
    
    private double calculateMultiplexingEfficiency() {
        // 计算相对于HTTP/1.1的性能提升
        // 基于实际并行处理的请求数量
        return 0.0; // 实际实现需要收集更多数据
    }
}

2. 调试工具

java

复制

下载

复制代码
/**
 * HTTP/2帧分析器(调试用)
 */
public class Http2FrameAnalyzer {
    /**
     * 分析帧序列
     */
    public void analyzeFrameSequence(List<Http2Frame> frames) {
        System.out.println("=== HTTP/2 Frame Sequence Analysis ===");
        System.out.printf("Total frames: %d\n", frames.size());
        
        Map<FrameType, Integer> frameCounts = new EnumMap<>(FrameType.class);
        int totalBytes = 0;
        
        for (Http2Frame frame : frames) {
            frameCounts.merge(frame.getType(), 1, Integer::sum);
            totalBytes += frame.getLength();
            
            // 检测潜在问题
            detectPotentialIssues(frame);
        }
        
        // 输出统计信息
        System.out.println("\nFrame Type Distribution:");
        frameCounts.forEach((type, count) -> {
            double percentage = (double) count / frames.size() * 100;
            System.out.printf("  %-15s: %d (%.1f%%)\n", type, count, percentage);
        });
        
        System.out.printf("\nTotal bytes: %d\n", totalBytes);
        System.out.printf("Average frame size: %.1f bytes\n", 
                         (double) totalBytes / frames.size());
    }
    
    private void detectPotentialIssues(Http2Frame frame) {
        // 检测队头阻塞
        if (frame.getType() == FrameType.DATA && frame.getStreamId() == 1) {
            // 检查流1是否阻塞了其他流
            detectHeadOfLineBlocking(frame);
        }
        
        // 检测流量控制问题
        if (frame.getType() == FrameType.WINDOW_UPDATE) {
            analyzeFlowControl(frame);
        }
        
        // 检测优先级问题
        if (frame.getType() == FrameType.PRIORITY) {
            analyzePriority(frame);
        }
    }
    
    /**
     * 头部压缩效果分析
     */
    public void analyzeHeaderCompression(List<HeaderField> original, 
                                        List<HeaderField> decoded) {
        int originalSize = calculateHeaderSize(original);
        int decodedSize = calculateHeaderSize(decoded);
        
        System.out.println("\n=== Header Compression Analysis ===");
        System.out.printf("Original size: %d bytes\n", originalSize);
        System.out.printf("Decoded size: %d bytes\n", decodedSize);
        System.out.printf("Compression ratio: %.1f%%\n", 
                         (double) decodedSize / originalSize * 100);
        
        // 分析哪些头部被压缩得最好
        analyzeIndividualHeaders(original, decoded);
    }
}

篇幅限制下面就只能给大家展示小册部分内容了。整理了一份核心面试笔记包括了:Java面试、Spring、JVM、MyBatis、Redis、MySQL、并发编程、微服务、Linux、Springboot、SpringCloud、MQ、Kafc

需要全套面试笔记及答案
【点击此处即可/免费获取】

七、生产环境部署建议

1. 服务器配置(Nginx示例)

nginx

复制

下载

复制代码
# nginx.conf
http {
    # 启用HTTP/2
    server {
        listen 443 ssl http2;
        
        # SSL配置(HTTP/2必须使用HTTPS)
        ssl_certificate /path/to/cert.pem;
        ssl_certificate_key /path/to/key.pem;
        
        # HTTP/2优化参数
        http2_max_field_size 16k;
        http2_max_header_size 32k;
        http2_max_requests 1000;
        http2_max_concurrent_streams 128;
        http2_recv_timeout 30s;
        
        # HPACK表大小
        http2_hpack_table_size 4096;
        
        # 连接超时设置
        http2_idle_timeout 3h;
        http2_chunk_size 8k;
        
        location / {
            # 反向代理配置
            proxy_http_version 1.1;
            proxy_set_header Connection "";
            
            # 保持与后端的长连接
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            
            # 重要的HTTP/2头部
            proxy_set_header :authority $host;
            proxy_set_header :scheme $scheme;
        }
    }
}

2. 客户端配置建议

java

复制

下载

复制代码
/**
 * 生产环境HTTP/2客户端配置
 */
public class ProductionHttp2ClientConfig {
    
    public Http2Client createOptimizedClient() {
        Http2Settings settings = new Http2Settings();
        
        // 优化设置
        settings.set(Setting.MAX_CONCURRENT_STREAMS, 100);  // 最大并发流
        settings.set(Setting.INITIAL_WINDOW_SIZE, 65535 * 2); // 增大初始窗口
        settings.set(Setting.MAX_FRAME_SIZE, 16384);        // 最大帧大小
        settings.set(Setting.HEADER_TABLE_SIZE, 4096);      // HPACK表大小
        settings.set(Setting.ENABLE_PUSH, 0);               // 禁用服务器推送
        
        // 连接参数
        settings.setConnectionTimeout(5000);     // 5秒连接超时
        settings.setReadTimeout(30000);          // 30秒读取超时
        settings.setWriteTimeout(30000);         // 30秒写入超时
        
        // 重试策略
        settings.setMaxRetries(3);
        settings.setRetryDelay(1000);            // 1秒重试延迟
        
        return new Http2Client(settings);
    }
    
    /**
     * 监控配置
     */
    public void setupMonitoring(Http2Client client) {
        // 启用详细日志
        client.enableFrameLogging(true);
        client.enableHeaderCompressionLogging(true);
        
        // 设置性能监控
        Http2Monitor monitor = new Http2Monitor();
        client.setMonitor(monitor);
        
        // 定期生成报告
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(() -> {
            PerformanceReport report = monitor.generateReport();
            logReport(report);
            
            // 根据报告调整参数
            if (report.getMultiplexingEfficiency() < 0.7) {
                adjustClientParameters(client);
            }
        }, 1, 5, TimeUnit.MINUTES); // 每5分钟
    }
}

八、性能对比与基准测试

HTTP/2 vs HTTP/1.1 性能对比

特性 HTTP/1.1 HTTP/2 性能提升
连接复用 需要多个TCP连接 单个TCP连接多路复用 减少TCP握手开销80%
头部压缩 无压缩,重复发送 HPACK压缩 减少头部大小60-90%
请求优先级 优先级树 关键资源优先加载
服务器推送 支持 减少额外请求延迟
队头阻塞 存在(请求级) 消除(帧级) 并行处理提升300%+

典型场景性能数据

text

复制

下载

复制代码
场景:加载包含100个资源的网页

HTTP/1.1(6个并行连接):
- TCP连接:6个
- 总请求数:100个
- 完成时间:4.2秒
- 头部开销:~40KB

HTTP/2(1个连接):
- TCP连接:1个
- 总请求数:100个  
- 完成时间:1.8秒(提升57%)
- 头部开销:~8KB(减少80%)

关键指标:
- 首字节时间:减少40%
- 完全加载时间:减少57%
- 带宽使用:减少55%

总结

HTTP/2的多路复用和头部压缩通过以下关键技术实现:

  1. 多路复用

    • 基于流的帧交换机制

    • 优先级树调度算法

    • 流量控制窗口管理

    • 避免队头阻塞

  2. 头部压缩

    • HPACK静态/动态表

    • Huffman编码

    • 增量索引机制

    • 高效的字符串编码

  3. 性能优化

    • 智能帧打包

    • 连接池管理

    • 自适应参数调整

    • 全面的监控体系

在实际部署中,HTTP/2通常能带来40-60%的性能提升,特别是在高延迟网络和高并发场景下效果显著。通过合理的配置和监控,可以最大化HTTP/2的性能优势。

相关推荐
宇木灵2 小时前
C语言基础学习-二、运算符
c语言·开发语言·学习
无心水2 小时前
【任务调度:数据库锁 + 线程池实战】3、 从 SELECT 到 UPDATE:深入理解 SKIP LOCKED 的锁机制与隔离级别
java·分布式·科技·spring·架构
yangSimaticTech2 小时前
沿触发的4个问题
开发语言·制造
编程小白gogogo2 小时前
苍穹外卖图片不显示解决教程
java·spring boot
hqyjzsb3 小时前
企业AI人才库的搭建体系与长效运营管理方案
人工智能·学习·职场和发展·创业创新·学习方法·业界资讯·改行学it
舟舟亢亢3 小时前
算法总结——二叉树【hot100】(上)
java·开发语言·算法
百锦再3 小时前
Java中的char、String、StringBuilder与StringBuffer 深度详解
java·开发语言·python·struts·kafka·tomcat·maven
上进小菜猪3 小时前
基于 YOLOv8 的水体污染目标检测系统 [目标检测完整源码]
后端
普通网友4 小时前
多协议网络库设计
开发语言·c++·算法
努力努力再努力wz4 小时前
【Linux网络系列】:TCP 的秩序与策略:揭秘传输层如何从不可靠的网络中构建绝对可靠的通信信道
java·linux·开发语言·数据结构·c++·python·算法