数据加密标准(DES)解析

概述

数据加密标准(Data Encryption Standard, DES)是1977年由美国国家标准局(NIST)采纳的对称密钥加密算法,作为首个公开的联邦信息处理标准(FIPS PUB 46)。DES采用64位分组 大小和56位有效密钥 长度(外加8位奇偶校验位),通过16轮Feistel网络结构实现数据加密。尽管已被AES取代,DES仍是理解现代密码学的基石。

历史意义

  • 首个公开的加密标准(1977-2005)
  • 推动了密码学研究的发展
  • 启发了3DES、AES等后续算法
  • 证明了Feistel网络的实际可行性

核心结构与特点

1. Feistel网络结构

复制代码
1. 64位明文输入
2. 初始置换(IP)
3. 分割为L0(左32位)和R0(右32位)
4. 16轮迭代处理:
   - 每轮:R_i = L_{i-1} ⊕ F(R_{i-1}, K_i)
   -        L_i = R_{i-1}
5. 合并R16和L16
6. 逆初始置换(IP⁻¹)
7. 输出64位密文

2. 密钥特性

特性 说明
有效长度 56位(64位含8位奇偶校验)
密钥调度 16轮子密钥生成算法
密钥空间 2⁵⁶ ≈ 7.2×10¹⁶种可能
密钥输入 64位(含8位校验位)
子密钥 16个48位轮密钥

3. 安全设计要素

  • 混淆:通过8个S盒实现非线性变换
  • 扩散:通过P盒置换实现比特扩散
  • 雪崩效应:1比特输入变化导致约50%输出比特改变
  • 抗差分分析:S盒精心设计抵抗差分攻击
  • 轮结构:16轮迭代确保充分混淆扩散

DES加解密详细过程

加密流程

复制代码
1. 64位明文输入
2. 初始置换IP
3. 分割为L0(左32位)和R0(右32位)
4. 16轮迭代:
   第1轮:R1 = L0 ⊕ F(R0, K1), L1 = R0
   第2轮:R2 = L1 ⊕ F(R1, K2), L2 = R1
   ...
   第16轮:R16 = L15 ⊕ F(R15, K16), L16 = R15
5. 合并R16和L16(注意:不交换最后结果)
6. 逆初始置换IP⁻¹
7. 输出64位密文

轮函数F核心处理

复制代码
1. 32位输入
2. 扩展置换E:32位→48位
3. 与48位轮密钥异或
4. S盒替换:48位→32位(8个S盒并行处理)
5. P盒置换:32位→32位
6. 32位输出

密钥调度算法

复制代码
1. 64位密钥输入
2. 置换选择PC-1:64位→56位(移除校验位)
3. 分割为C0(左28位)和D0(右28位)
4. 16轮处理:
   - 循环左移(1或2位,根据轮次)
   - 置换选择PC-2:56位→48位(生成轮密钥Ki)
5. 输出16个48位轮密钥(K1-K16)

Java实现(完整注释版)

java 复制代码
import java.security.SecureRandom;
import java.util.Arrays;

public class DESAlgorithm {
    private static final int BLOCK_SIZE = 8; // DES分组大小
    // 初始置换表 (IP)
    private static final int[] INITIAL_PERMUTATION = {
            58, 50, 42, 34, 26, 18, 10, 2,
            60, 52, 44, 36, 28, 20, 12, 4,
            62, 54, 46, 38, 30, 22, 14, 6,
            64, 56, 48, 40, 32, 24, 16, 8,
            57, 49, 41, 33, 25, 17, 9,  1,
            59, 51, 43, 35, 27, 19, 11, 3,
            61, 53, 45, 37, 29, 21, 13, 5,
            63, 55, 47, 39, 31, 23, 15, 7
    };

    // 逆初始置换表 (IP⁻¹)
    private static final int[] INVERSE_PERMUTATION = {
            40, 8, 48, 16, 56, 24, 64, 32,
            39, 7, 47, 15, 55, 23, 63, 31,
            38, 6, 46, 14, 54, 22, 62, 30,
            37, 5, 45, 13, 53, 21, 61, 29,
            36, 4, 44, 12, 52, 20, 60, 28,
            35, 3, 43, 11, 51, 19, 59, 27,
            34, 2, 42, 10, 50, 18, 58, 26,
            33, 1, 41, 9, 49, 17, 57, 25
    };

    // 扩展置换表 (E)
    private static final int[] EXPANSION_TABLE = {
            32,  1,  2,  3,  4,  5,
            4,  5,  6,  7,  8,  9,
            8,  9, 10, 11, 12, 13,
            12, 13, 14, 15, 16, 17,
            16, 17, 18, 19, 20, 21,
            20, 21, 22, 23, 24, 25,
            24, 25, 26, 27, 28, 29,
            28, 29, 30, 31, 32,  1
    };

    // DES标准的8个S盒定义(4行×16列)
    private static final int[][][] S_BOXES = {
            // S1
            {
                    {14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7},
                    {0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8},
                    {4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0},
                    {15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13}
            },
            // S2
            {
                    {15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10},
                    {3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5},
                    {0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15},
                    {13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9}
            },
            // S3
            {
                    {10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8},
                    {13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1},
                    {13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7},
                    {1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12}
            },
            // S4
            {
                    {7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15},
                    {13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9},
                    {10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4},
                    {3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14}
            },
            // S5
            {
                    {2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9},
                    {14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6},
                    {4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14},
                    {11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3}
            },
            // S6
            {
                    {12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11},
                    {10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8},
                    {9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6},
                    {4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13}
            },
            // S7
            {
                    {4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1},
                    {13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6},
                    {1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2},
                    {6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12}
            },
            // S8
            {
                    {13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7},
                    {1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2},
                    {7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8},
                    {2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11}
            }
    };
    // P盒置换表
    private static final int[] P_BOX_PERMUTATION = {
            16,  7, 20, 21,
            29, 12, 28, 17,
            1, 15, 23, 26,
            5, 18, 31, 10,
            2,  8, 24, 14,
            32, 27,  3,  9,
            19, 13, 30,  6,
            22, 11,  4, 25
    };

    // 密钥置换表 (PC-1)
    private static final int[] PC1 = {
            57, 49, 41, 33, 25, 17, 9,
            1, 58, 50, 42, 34, 26, 18,
            10,  2, 59, 51, 43, 35, 27,
            19, 11,  3, 60, 52, 44, 36,
            63, 55, 47, 39, 31, 23, 15,
            7, 62, 54, 46, 38, 30, 22,
            14,  6, 61, 53, 45, 37, 29,
            21, 13,  5, 28, 20, 12,  4
    };

    // 轮移位表
    private static final int[] SHIFT_SCHEDULE = {
            1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1
    };

    // 密钥压缩置换 (PC-2)
    private static final int[] PC2 = {
            14, 17, 11, 24,  1,  5,
            3, 28, 15,  6, 21, 10,
            23, 19, 12,  4, 26,  8,
            16,  7, 27, 20, 13,  2,
            41, 52, 31, 37, 47, 55,
            30, 40, 51, 45, 33, 48,
            44, 49, 39, 56, 34, 53,
            46, 42, 50, 36, 29, 32
    };

    /**
     * 执行位置换操作
     * @param input 输入位数组
     * @param table 置换表
     * @return 置换后的位数组
     */
    private static boolean[] permute(boolean[] input, int[] table) {
        boolean[] output = new boolean[table.length];
        for (int i = 0; i < table.length; i++) {
            output[i] = input[table[i] - 1];//注意减去1,因为数组从0开始
        }
        return output;
    }

    /**
     * 执行S盒替换 (DES核心非线性变换)
     * @param input 48位输入
     * @return 32位输出
     */
    private static boolean[] sBoxSubstitution(boolean[] input) {
        if (input.length != 48)
            throw new IllegalArgumentException("S盒输入必须为48位");

        boolean[] output = new boolean[32];

        for (int i = 0; i < 8; i++) {
            // 提取6位输入
            boolean[] bs = Arrays.copyOfRange(input, i*6, (i+1)*6);

            // 计算行号 (第1和6位)
            int row = (bs[0] ? 2 : 0) | (bs[5] ? 1 : 0);//b1和b6位组合

            // 计算列号 (中间4位)
            int col = 0;
            for (int j = 1; j <= 4; j++) {
                if (bs[j]) col |= (1 << (4 - j));//中间4位组合
            }

            // 从S盒获取4位输出
            int sBoxValue = S_BOXES[i][row][col];
            // 转换为4位布尔数组
            int outputPos = 0;
            for (int j = 0; j < 4; j++) {
                output[outputPos++] = ((sBoxValue >> (3 - j)) & 1) == 1;
            }
        }

        return output;
    }

    /**
     * 轮函数F
     * @param right 32位右半部分
     * @param roundKey 48位轮密钥
     * @return 32位输出
     */
    private static boolean[] fFunction(boolean[] right, boolean[] roundKey) {
        // 1. 扩展置换 (32→48位)
        boolean[] expanded = permute(right, EXPANSION_TABLE);

        // 2. 与轮密钥异或
        boolean[] xored = new boolean[48];
        for (int i = 0; i < 48; i++) {
            xored[i] = expanded[i] ^ roundKey[i];
        }

        // 3. S盒替换 (48→32位)
        boolean[] substituted = sBoxSubstitution(xored);

        // 4. P盒置换
        return permute(substituted, P_BOX_PERMUTATION);
    }

    /**
     * 密钥调度算法 (生成16轮子密钥)
     * @param key 64位密钥 (含8位奇偶校验)
     * @return 16个48位轮密钥
     */
    private static boolean[][] generateSubKeys(boolean[] key) {
        // 1. PC-1置换 (64→56位)
        boolean[] pc1 = permute(key, PC1);

        // 2. 分为左右28位
        boolean[] c0 = Arrays.copyOfRange(pc1, 0, 28);
        boolean[] d0 = Arrays.copyOfRange(pc1, 28, 56);

        boolean[][] subKeys = new boolean[16][48];

        for (int round = 0; round < 16; round++) {
            // 3. 循环左移
            int shift = SHIFT_SCHEDULE[round];
            c0 = rotateLeft(c0, shift);
            d0 = rotateLeft(d0, shift);

            // 4. 合并为56位
            boolean[] cd = new boolean[56];
            System.arraycopy(c0, 0, cd, 0, 28);
            System.arraycopy(d0, 0, cd, 28, 28);

            // 5. PC-2置换 (56→48位)
            subKeys[round] = permute(cd, PC2);
        }

        return subKeys;
    }

    /**
     * 循环左移
     * @param bits 输入位数组
     * @param shift 移位位数
     * @return 移位后位数组
     */
    private static boolean[] rotateLeft(boolean[] bits, int shift) {
        boolean[] result = new boolean[bits.length];
        for (int i = 0; i < bits.length; i++) {
            result[i] = bits[(i + shift) % bits.length];
        }
        return result;
    }

    /**
     * DES加密核心
     * @param block 64位明文分组
     * @param key 64位密钥
     * @return 64位密文分组
     */
    public static boolean[] desEncryptBlock(boolean[] block, boolean[] key) {
        // 1. 生成16轮子密钥
        boolean[][] subKeys = generateSubKeys(key);

        // 2. 初始置换IP
        boolean[] ip = permute(block, INITIAL_PERMUTATION);

        // 3. 分为左右32位
        boolean[] left = Arrays.copyOfRange(ip, 0, 32);
        boolean[] right = Arrays.copyOfRange(ip, 32, 64);

        // 4. 16轮Feistel迭代
        for (int round = 0; round < 16; round++) {
            boolean[] temp = right;
            boolean[] fResult = fFunction(right, subKeys[round]);

            // 左半部分与F函数结果异或
            boolean[] newRight = new boolean[32];
            for (int i = 0; i < 32; i++) {
                newRight[i] = left[i] ^ fResult[i];
            }

            // 更新左右部分
            left = temp;
            right = newRight;
        }

        // 5. 最终交换 (R16  L16)
        boolean[] rl = new boolean[64];
        System.arraycopy(right, 0, rl, 0, 32);
        System.arraycopy(left, 0, rl, 32, 32);

        // 6. 逆初始置换IP⁻¹
        return permute(rl, INVERSE_PERMUTATION);
    }

    /**
     * DES解密核心
     * @param block 64位密文分组
     * @param key 64位密钥
     * @return 64位明文分组
     */
    public static boolean[] desDecryptBlock(boolean[] block, boolean[] key) {
        // 生成子密钥
        boolean[][] subKeys = generateSubKeys(key);

        // 解密时逆序使用子密钥
        boolean[][] reverseKeys = new boolean[16][48];
        for (int i = 0; i < 16; i++) {
            reverseKeys[i] = subKeys[15 - i];
        }

        // 其余步骤与加密相同
        boolean[] ip = permute(block, INITIAL_PERMUTATION);
        boolean[] left = Arrays.copyOfRange(ip, 0, 32);
        boolean[] right = Arrays.copyOfRange(ip, 32, 64);

        for (int round = 0; round < 16; round++) {
            boolean[] temp = right;
            boolean[] fResult = fFunction(right, reverseKeys[round]);

            boolean[] newRight = new boolean[32];
            for (int i = 0; i < 32; i++) {
                newRight[i] = left[i] ^ fResult[i];
            }

            left = temp;
            right = newRight;
        }

        boolean[] rl = new boolean[64];
        System.arraycopy(right, 0, rl, 0, 32);
        System.arraycopy(left, 0, rl, 32, 32);

        return permute(rl, INVERSE_PERMUTATION);
    }

    /**
     * 字节数组转位数组
     * @param bytes 字节数组
     * @return 位数组
     */
    public static boolean[] bytesToBits(byte[] bytes) {
        boolean[] bits = new boolean[bytes.length * 8];
        for (int i = 0; i < bytes.length; i++) {
            for (int j = 0; j < 8; j++) {
                bits[i * 8 + j] = ((bytes[i] >> (7 - j)) & 1) == 1;
            }
        }
        return bits;
    }

    /**
     * 位数组转字节数组
     * @param bits 位数组
     * @return 字节数组
     */
    public static byte[] bitsToBytes(boolean[] bits) {
        byte[] bytes = new byte[bits.length / 8];
        for (int i = 0; i < bytes.length; i++) {
            for (int j = 0; j < 8; j++) {
                if (bits[i * 8 + j]) {
                    bytes[i] |= (1 << (7 - j));
                }
            }
        }
        return bytes;
    }

    public static byte[] generateRandomKey() {
        SecureRandom random = new SecureRandom();
        byte[] key = new byte[8]; // 64位密钥
        random.nextBytes(key);

        // 设置奇偶校验位(DES标准要求)
        for (int i = 0; i < 8; i++) {
            int parity = Integer.bitCount(key[i] & 0xFF) % 2;
            key[i] = (byte) ((key[i] & 0xFE) | (parity == 0 ? 1 : 0));
        }
        return key;
    }

    /**
     * PKCS#7填充
     * @param data 需要填充的数据
     * @param blockSize 块大小(DES为8字节)
     * @return 填充后的数据
     */
    public static byte[] pkcs7Pad(byte[] data, int blockSize) {
        int paddingLength = blockSize - (data.length % blockSize);
        byte[] padded = new byte[data.length + paddingLength];
        System.arraycopy(data, 0, padded, 0, data.length);

        // 填充字节的值等于填充长度
        Arrays.fill(padded, data.length, padded.length, (byte) paddingLength);

        return padded;
    }

    /**
     * PKCS#7去除填充
     * @param data 带填充的数据
     * @return 去除填充后的数据
     * @throws IllegalArgumentException 如果填充无效
     */
    public static byte[] pkcs7Unpad(byte[] data) {
        // 检查填充长度是否有效
        int paddingLength = data[data.length - 1] & 0xFF;
        if (paddingLength < 1 || paddingLength > data.length) {
            throw new IllegalArgumentException("无效的PKCS#7填充");
        }

        // 验证所有填充字节是否正确
        for (int i = data.length - paddingLength; i < data.length; i++) {
            if (data[i] != paddingLength) {
                throw new IllegalArgumentException("无效的PKCS#7填充");
            }
        }

        return Arrays.copyOfRange(data, 0, data.length - paddingLength);
    }

    /**
     * DES加密长文本
     * @param plaintext 明文
     * @param key 64位密钥(8字节)
     * @return 密文
     */
    public static byte[] encrypt(byte[] plaintext, byte[] key) {
        // 1. 应用PKCS#7填充
        byte[] padded = pkcs7Pad(plaintext, 8);

        // 2. 将密钥转换为位数组
        boolean[] keyBits = bytesToBits(key);

        // 3. 分组加密
        byte[] ciphertext = new byte[padded.length];

        for (int i = 0; i < padded.length; i += 8) {
            // 提取当前分组
            byte[] block = Arrays.copyOfRange(padded, i, i + 8);
            boolean[] blockBits = bytesToBits(block);

            // 加密分组
            boolean[] encryptedBits = desEncryptBlock(blockBits, keyBits);

            // 转换回字节并存储
            byte[] encryptedBlock = bitsToBytes(encryptedBits);
            System.arraycopy(encryptedBlock, 0, ciphertext, i, 8);
        }

        return ciphertext;
    }

    /**
     * DES解密长文本
     * @param ciphertext 密文
     * @param key 64位密钥(8字节)
     * @return 明文
     */
    public static byte[] decrypt(byte[] ciphertext, byte[] key) {
        // 1. 检查输入长度
        if (ciphertext.length % 8 != 0) {
            throw new IllegalArgumentException("密文长度必须是8的倍数");
        }

        // 2. 将密钥转换为位数组
        boolean[] keyBits = bytesToBits(key);

        // 3. 分组解密
        byte[] decryptedWithPadding = new byte[ciphertext.length];

        for (int i = 0; i < ciphertext.length; i += 8) {
            // 提取当前分组
            byte[] block = Arrays.copyOfRange(ciphertext, i, i + 8);
            boolean[] blockBits = bytesToBits(block);

            // 解密分组
            boolean[] decryptedBits = desDecryptBlock(blockBits, keyBits);

            // 转换回字节并存储
            byte[] decryptedBlock = bitsToBytes(decryptedBits);
            System.arraycopy(decryptedBlock, 0, decryptedWithPadding, i, 8);
        }

        // 4. 去除填充
        return pkcs7Unpad(decryptedWithPadding);
    }

}
  • 演示
java 复制代码
public class Main {
    public static void main(String[] args) {
        byte[] key=DESAlgorithm.generateRandomKey();//生成随机密钥
        String str="尽管DES已完成历史使命,其精巧的Feistel结构设计、S盒构造方法和密钥调度算法,仍为密码学研究者提供了宝贵的学习范例。理解DES的工作原理,是掌握现代密码学不可或缺的基础。";
        byte[] msg= str.getBytes();
        byte[] c = DESAlgorithm.encrypt(msg, key);//加密
        String encryptedText = new String(c);
        byte[] m = DESAlgorithm.decrypt(c,key);//解密
        String decryptedText = new String(m);
        System.out.println(encryptedText);
        System.out.println(decryptedText);
    }
}

DES安全分析与演进

安全漏洞

  1. 密钥长度不足:56位密钥易受暴力破解
  2. 弱密钥问题:某些密钥导致加密强度降低
  3. 半弱密钥:某些密钥对产生可逆加密
  4. 线性与差分分析:理论攻击方法复杂度为2⁴³

增强方案:3DES

复制代码
加密:C = E(K3, D(K2, E(K1, P)))
解密:P = D(K1, E(K2, D(K3, C)))

3DES Java实现

java 复制代码
public static byte[] tripleDESEncrypt(byte[] plaintext, byte[] key) throws Exception {
    if (key.length != 24) throw new IllegalArgumentException("3DES需要24字节密钥");
    
    byte[] key1 = Arrays.copyOfRange(key, 0, 8);
    byte[] key2 = Arrays.copyOfRange(key, 8, 16);
    byte[] key3 = Arrays.copyOfRange(key, 16, 24);
    
    // 加密:E(K3, D(K2, E(K1, P)))
    byte[] step1 = encryptWithJCE(plaintext, key1);
    byte[] step2 = decryptWithJCE(step1, key2);
    return encryptWithJCE(step2, key3);
}

DES与AES对比

特性 DES AES
密钥长度 56位 128/192/256位
分组大小 64位 128位
结构 Feistel网络 SP网络
轮数 16 10/12/14
安全性 已破解 当前安全
性能 较慢 更快
应用 遗留系统 现代标准

实际应用建议

  1. 避免使用纯DES:使用3DES或AES替代

  2. 选择安全模式

    java 复制代码
    // 推荐使用CBC或CTR模式
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
  3. 密钥管理最佳实践

    java 复制代码
    // 使用安全随机数生成密钥
    KeyGenerator keyGen = KeyGenerator.getInstance("AES");
    keyGen.init(256); // 256位密钥
    SecretKey secretKey = keyGen.generateKey();
  4. 完整性保护

    java 复制代码
    // 结合HMAC或使用认证加密模式
    Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");

历史意义与总结

DES作为密码学发展史上的里程碑:

  1. 开创了公开密码算法的标准化时代
  2. 推动了分组密码设计理论研究
  3. 促进了密码分析技术的进步
  4. 为现代加密标准(如AES)奠定了基础

尽管DES已完成历史使命,其精巧的Feistel结构设计、S盒构造方法和密钥调度算法,仍为密码学研究者提供了宝贵的学习范例。理解DES的工作原理,是掌握现代密码学不可或缺的基础。

相关推荐
月夕·花晨20 小时前
Gateway-过滤器
java·分布式·spring·spring cloud·微服务·gateway·sentinel
hssfscv21 小时前
JAVA学习笔记——9道综合练习习题+二维数组
java·笔记·学习
初听于你1 天前
缓存技术揭秘
java·运维·服务器·开发语言·spring·缓存
小蒜学长1 天前
springboot多功能智能手机阅读APP设计与实现(代码+数据库+LW)
java·spring boot·后端·智能手机
zizisuo1 天前
解决在使用Lombok时maven install 找不到符号的问题
java·数据库·maven
笨蛋少年派1 天前
JAVA基础语法
java·开发语言
Haooog1 天前
654.最大二叉树(二叉树算法)
java·数据结构·算法·leetcode·二叉树
我真的是大笨蛋1 天前
依赖倒置原则(DIP)
java·设计模式·性能优化·依赖倒置原则·设计规范
北京耐用通信1 天前
耐达讯自动化Modbus RTU转Profibus,让电磁阀连接从此与众不同!
网络·人工智能·网络协议·网络安全·自动化
东方芷兰1 天前
JavaWeb 课堂笔记 —— 20 SpringBootWeb案例 配置文件
java·开发语言·笔记·算法·log4j·intellij-idea·lua