Pre
加密与安全_探索密钥交换算法(Diffie-Hellman算法) 中我们可以看到,公钥-私钥组成的密钥对是非常有用的加密方式,因为公钥是可以公开的,而私钥是完全保密的,由此奠定了非对称加密的基础。
非对称加密确实是一种非常有用的加密方式,其基础就是公钥和私钥组成的密钥对。公钥可以公开,而私钥则是完全保密的,这种特性为非对称加密提供了很好的安全性。
在非对称加密中,加密和解密使用的不是相同的密钥。只有同一个密钥对中的公钥和私钥才能正常地进行加密和解密操作 。
因此,如果小明要向小红发送加密文件,他应该首先向小红索取她的公钥。然后,小明使用小红的公钥对文件进行加密,并将加密后的文件发送给小红。由于只有小红持有与公钥对应的私钥,因此只有小红能够使用她的私钥解密这个文件。其他人无法使用公钥解密文件,因为只有私钥的持有者才能解密数据。
这种方式确保了文件在传输过程中的安全性,只有具备私钥的接收方才能解密文件,保护了通信的机密性。
主流的非对称加密算法
主流的非对称加密算法包括:
- RSA(Rivest-Shamir-Adleman): RSA是最常用的非对称加密算法之一,它基于大数分解的数学难题。RSA算法可以用于加密、数字签名和密钥交换等场景,广泛应用于网络通信、数据传输和身份认证等领域。
- DSA(Digital Signature Algorithm): DSA是一种数字签名算法,专门用于生成和验证数字签名,常用于身份认证、数据完整性验证等场景。DSA算法基于离散对数的数学难题,相对于RSA算法,它的加密和解密速度更快。
- ECC(Elliptic Curve Cryptography): ECC是一种基于椭圆曲线的非对称加密算法,具有与RSA相当的安全性,但在密钥长度较短的情况下提供了更高的安全性,因此在资源受限的环境下更加适用。
- ElGamal: ElGamal是一种基于离散对数的非对称加密算法,主要用于密钥交换和加密通信。与RSA类似,ElGamal算法也可以用于加密和数字签名,但相对于RSA,ElGamal算法在实现和使用上更为复杂。
这些非对称加密算法在不同的场景下有着各自的优缺点和适用性,选择合适的算法取决于具体的安全需求、性能要求和应用环境。
典型算法:RSA
非对称加密的典型算法就是RSA算法,它是由Ron Rivest、Adi Shamir和Leonard Adleman这三位密码学家共同发明的,因此用他们三人的姓的首字母缩写表示。
相比对称加密,非对称加密有显著的优点。对称加密需要在通信双方之间协商共享密钥,而非对称加密则可以安全地公开各自的公钥。
- 在N个人之间进行通信时,使用非对称加密只需要N个密钥对,每个人只需管理自己的密钥对。
- 而使用对称加密时,则需要
N*(N-1)/2
个密钥,因此每个人需要管理N-1
个密钥,密钥管理的难度大,并且容易导致密钥泄漏。
非对称加密的缺点就是运算速度非常慢,比对称加密要慢很多。 在实际应用中,通常会将非对称加密和对称加密结合使用,以充分发挥它们各自的优点,并弥补彼此的缺点。
假设小明需要向小红传输加密文件,他们可以采取以下步骤:
- 小明和小红首先交换各自的公钥,这可以在安全的通信渠道上完成。
- 小明生成一个随机的对称密钥(比如AES口令),然后使用小红的公钥通过RSA算法对这个对称密钥进行加密,得到密文。
- 小明将加密后的对称密钥发送给小红。
- 小红使用自己的RSA私钥对收到的密文进行解密,得到原始的对称密钥。
- 现在,小明和小红都拥有相同的对称密钥,他们可以使用对称加密算法(如AES)来加密和解密通信内容。由于对称加密算法的运算速度快,因此通信双方可以更高效地进行加密通信。
通过这种方式,非对称加密用于安全地传输对称密钥,而对称加密用于加密和解密实际的通信内容,既保证了安全性,又提高了效率。
Code
package com.artisan.securityalgjava.rsa;
import javax.crypto.Cipher;
import java.security.*;
import java.util.Base64;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
public class RSAExample {
public static void main(String[] args) throws Exception {
// 生成RSA密钥对
KeyPair keyPair = generateRSAKeyPair();
// 获取公钥和私钥
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 要加密的原始数据
String plaintext = "Hello, RSA!";
System.out.println("Plaintext: " + plaintext);
// 使用公钥加密数据
byte[] encryptedBytes = encryptRSA(plaintext.getBytes(), publicKey);
String encryptedText = Base64.getEncoder().encodeToString(encryptedBytes);
System.out.println("Encrypted text: " + encryptedText);
// 使用私钥解密数据
byte[] decryptedBytes = decryptRSA(Base64.getDecoder().decode(encryptedText), privateKey);
String decryptedText = new String(decryptedBytes);
System.out.println("Decrypted text: " + decryptedText);
}
// 生成RSA密钥对
public static KeyPair generateRSAKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048); // 设置密钥长度为2048位
return keyPairGenerator.generateKeyPair();
}
// 使用公钥加密数据
public static byte[] encryptRSA(byte[] data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
}
// 使用私钥解密数据
public static byte[] decryptRSA(byte[] encryptedData, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(encryptedData);
}
}
输出:
Plaintext: Hello, RSA!
Encrypted text: CU+9ZlNmH3NR5SZSdpZRQmTyXQLU0SLX1d1ICoi/LjAu6a4lMsVXUfB+STFHglKOTVCEjddCJwoWVGXJDcm/nTqRVtB/qIV3+kt7W4/H2fvLeYf+mgbIyNNMyZU4h7k0rbVSTIdHpmfcV1ToYPNVtYarHQ0EbmD261iKeGBGb9HrprknspsXxfIMAEeKOEFotE/4fkwySCsWatANwOwjivEqZcDBnX/1BlhA9CIP4zd4osUoiRt/wJtBsx58/A+47Lf2wBo7C8YcRRpa2A8HxtxnOkhy0cciVeaAzMtaaiVDWbaBqML3sXu3iP3CDkrZQ2+VmBCTZX35y1fxCZ1/Dg==
Decrypted text: Hello, RSA!
package com.artisan.securityalgjava.rsa;
import javax.crypto.Cipher;
import java.math.BigInteger;
import java.security.*;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
public class RsaDemo {
public static void main(String[] args) throws Exception {
// 明文:
byte[] plain = "Hello, encrypt use RSA".getBytes("UTF-8");
// 创建公钥/私钥对
Person alice = new Person("Alice");
// 用Alice的公钥加密:
byte[] pk = alice.getPublicKey();
System.out.println(String.format("public key: %x", new BigInteger(1, pk)));
byte[] encrypted = alice.encrypt(plain);
System.out.println(String.format("encrypted: %x", new BigInteger(1, encrypted)));
// 用Alice的私钥解密:
byte[] sk = alice.getPrivateKey();
System.out.println(String.format("private key: %x", new BigInteger(1, sk)));
byte[] decrypted = alice.decrypt(encrypted);
System.out.println(new String(decrypted, "UTF-8"));
}
}
class Person {
String name;
// 私钥:
PrivateKey sk;
// 公钥:
PublicKey pk;
public Person(String name) throws GeneralSecurityException {
this.name = name;
// 生成公钥/私钥对:
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
kpGen.initialize(1024);
KeyPair kp = kpGen.generateKeyPair();
this.sk = kp.getPrivate();
this.pk = kp.getPublic();
}
/**
* 把私钥导出为字节
* @return
*/
public byte[] getPrivateKey() {
return this.sk.getEncoded();
}
/**
* 把公钥导出为字节
* @return
*/
public byte[] getPublicKey() {
return this.pk.getEncoded();
}
/**
* 用公钥加密
* @param message
* @return
* @throws GeneralSecurityException
*/
public byte[] encrypt(byte[] message) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, this.pk);
return cipher.doFinal(message);
}
/**
* 用私钥解密
* @param input
* @return
* @throws GeneralSecurityException
*/
public byte[] decrypt(byte[] input) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, this.sk);
return cipher.doFinal(input);
}
}
输出:
public key: 30819f300d06092a864886f70d010101050003818d00308189028181008c97547fab56c9af0f54d814581a7d695e0722c7029751e4d05530d28923c45ac29420e8cec253a71043e98dae2139cee1d97e125f73c0b32a91196a7154c3199cf6529942627e50796463fc7ab6c747ab0788fe67d3855c9f9348233277ef68d663c2df1232c5adb6d7d9be0aefe5e6c0bcdda48abf9570d732878b93c5bc6d0203010001
encrypted: 24bac571932a8f2c633660493a57e895e404c03824ba8fff87bfaaa655914eaeabd032beed102d24479c21e1c17ccfea5018708da42e28ec7e2f472b36f769f0aa0639bdff985babdbce3a9d357a89dcf31df2b00366f16708245c2a75a3c5d25aedd25207f3204416a6e431f305bd49b90eb0d40d6caed2bb4f068c183fb442
private key: 30820275020100300d06092a864886f70d01010105000482025f3082025b020100028181008c97547fab56c9af0f54d814581a7d695e0722c7029751e4d05530d28923c45ac29420e8cec253a71043e98dae2139cee1d97e125f73c0b32a91196a7154c3199cf6529942627e50796463fc7ab6c747ab0788fe67d3855c9f9348233277ef68d663c2df1232c5adb6d7d9be0aefe5e6c0bcdda48abf9570d732878b93c5bc6d02030100010281806b2753f1d2875d449decce9c02e27dbf7738fd1aad30e3ebff954e96c88b88369ca305ca2afc1581f975a966a0d716164630dc53e88872d09b9ae7c2270ab17f80e36bc78532d68216ecb3a62bc4ab84be8b6db08c48568622f2216e29609f3ae6db825c13d503554a923fe62850cd6fcf2221315c98946c3fd47c79f6bc5089024100fcc240d520a5106544a5cc764c9149c35fca005b9af0fcb5999fa5e4b69a1a5ca19f732e5a2633c0ceeceb7cc433e39f5fca0c7c9a1679ff9990029170adfe070241008e64db6f5b7270a69d4082c88db9c8c9909519b535bd8bf898f2fc6f3d7ac00c1fa8d2fa4614cb851f64f7bea021d60e5d27fe5f5aa0c21e0f05ef5f54e314eb0240460aca8e850258ddc73d2ec0a58d2964b3b9b589ad1114e67a10cc96e9a720a104c4bbd55f73f0a9806e14ffb91b2bfbb13ebb61180e1c76a126501fdf9ac7a7024020845bb0045c0fe99c837cda3bb32f6d083d644f836433b0a38ce9a4a58f8087c43b1362dfda23d7d4a18409de1b9bfc4fbdb0532a2907eb415703a0eb8ba7dd02401fee6a8ef7af9c5c723215500003984e5ded1bd31061ce5396bf5b55ab7cdbe8b6024863fd466eb8de13318863f485af479cc66b8d8a858f02b9b3254b0c1562
Hello, encrypt use RSA
RSA的公钥和私钥的恢复
RSA的公钥和私钥都可以通过getEncoded()方法获得以byte[]表示的二进制数据,并根据需要保存到文件中。
伪代码如下:
byte[] pkData = ...
byte[] skData = ...
KeyFactory kf = KeyFactory.getInstance("RSA");
// 恢复公钥:
X509EncodedKeySpec pkSpec = new X509EncodedKeySpec(pkData);
PublicKey pk = kf.generatePublic(pkSpec);
// 恢复私钥:
PKCS8EncodedKeySpec skSpec = new PKCS8EncodedKeySpec(skData);
PrivateKey sk = kf.generatePrivate(skSpec);
完整代码:
package com.artisan.securityalgjava.rsa;
import java.security.*;
import java.security.spec.*;
import java.util.Base64;
/**
* @author 小工匠
* @version 1.0
* @mark: show me the code , change the world
*/
public class RSAKeyConversionExample {
public static void main(String[] args) throws Exception {
// 生成RSA密钥对
KeyPair keyPair = generateRSAKeyPair();
// 获取公钥和私钥
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 将公钥和私钥转换为byte[]数组
byte[] publicKeyBytes = publicKey.getEncoded();
byte[] privateKeyBytes = privateKey.getEncoded();
// 将byte[]数组转换为Base64编码的字符串方便保存和传输
String publicKeyBase64 = Base64.getEncoder().encodeToString(publicKeyBytes);
String privateKeyBase64 = Base64.getEncoder().encodeToString(privateKeyBytes);
System.out.println("Public key (Base64): " + publicKeyBase64);
System.out.println("Private key (Base64): " + privateKeyBase64);
// 从Base64编码的字符串恢复公钥和私钥
PublicKey restoredPublicKey = restorePublicKey(Base64.getDecoder().decode(publicKeyBase64));
PrivateKey restoredPrivateKey = restorePrivateKey(Base64.getDecoder().decode(privateKeyBase64));
// 验证恢复的公钥和私钥与原始的是否一致
System.out.println("Restored public key equals original: " + publicKey.equals(restoredPublicKey));
System.out.println("Restored private key equals original: " + privateKey.equals(restoredPrivateKey));
}
// 生成RSA密钥对
public static KeyPair generateRSAKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048); // 设置密钥长度为2048位
return keyPairGenerator.generateKeyPair();
}
/**
* 从byte[]数组恢复公钥
* @param publicKeyBytes
* @return
* @throws Exception
*/
public static PublicKey restorePublicKey(byte[] publicKeyBytes) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
return keyFactory.generatePublic(publicKeySpec);
}
/**
* 从byte[]数组恢复私钥
* @param privateKeyBytes
* @return
* @throws Exception
*/
public static PrivateKey restorePrivateKey(byte[] privateKeyBytes) throws Exception {
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
return keyFactory.generatePrivate(privateKeySpec);
}
}
输出
Public key (Base64): MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApd4mRIUmxWx9+Jrp/Gp06gqRbgsGdy36Dshvrkz5ofiKe6guRfP/swimmsdZItPpfWJNn2BKQzTL32gSiZ5y+uU7EpkEDmg2Z6ip5jb0Dpt0/cTldD7ykG6AMlnpJwoFQXgNTCpgqOOhFRNRPTBLUjeVMeNeJPoVQKipdFTVrUU5NCZSHgvBWjngELmLZDdj1sJ/vCyEol1eg9N1G8c9kcyhbAhSGX7BSq3q/heuOPGjzHVTMJb2mKvGPst7fYdmUyVvW+ovF7AMf8AfemLrdZJ+mxi4dj2X5c2fKcNK32+zfJNhpQMdaLZtbHAAyvSVc6xGxiYYGAuUrFnGiY26XwIDAQAB
Private key (Base64): MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCl3iZEhSbFbH34mun8anTqCpFuCwZ3LfoOyG+uTPmh+Ip7qC5F8/+zCKaax1ki0+l9Yk2fYEpDNMvfaBKJnnL65TsSmQQOaDZnqKnmNvQOm3T9xOV0PvKQboAyWeknCgVBeA1MKmCo46EVE1E9MEtSN5Ux414k+hVAqKl0VNWtRTk0JlIeC8FaOeAQuYtkN2PWwn+8LISiXV6D03Ubxz2RzKFsCFIZfsFKrer+F6448aPMdVMwlvaYq8Y+y3t9h2ZTJW9b6i8XsAx/wB96Yut1kn6bGLh2PZflzZ8pw0rfb7N8k2GlAx1otm1scADK9JVzrEbGJhgYC5SsWcaJjbpfAgMBAAECggEAMndY5Vgt57uOyGk58Bbj46G7hePM384ZWw4ZDMtW2LUqTV1qVtZaYjqrlkQ5FqOrUv7p5ygA8FnL/flISp7vFO9R/eKYnVmP1BI5P1ZRA3DBM8UIm0nbu54jWy6IBdzCpJzGTvpF1p0LkcIC4b8j66wFtNwc9NsyRC4NANwe90ytm6/HrsoUlRgMeJv9QejfA8A3+VrF1Y1ozn7aSuElX6q7tdZ0kNEGN7qVdl1qilmcMiPpMLC5k7J7WpTeVZGeQM3Akm2LnbcAS5QMkSyhSZWj0n3e+kGRdmCBdRRCGQVsk2qeTahYZvtv1hJRe0mUbDEC/DWSDFLJudbSa5GAAQKBgQDxMFGpiQZ5IWrxh4Rh2CPJpZB1UfQjC5NI0kcKx8jXGp/GXoGfpcjmmF7yRg9ASeG1oOioZKdktSo7KRsSLKetG+L3W+pHkPaPMz/Vt788XuWAZKlBUJ0+02czeRjXDXY0B6Ik4uJ9okRuKO4EndWVhT/bSC2f6khergOe6HPqXwKBgQCwDbllyfGsRS0yn31KcNTjG2E9FMh9uhRP+0wcJR1dDqsXimQ1KE1cdWRsGWolA5oXaR8jNtSUc3NhuyYnjSoI/S2Z8VYnKdfDNb8x6pPV1RxCW5gRT2vrRxBnnN9bkI5ABhRKQslmAq+I0mYAueUGU+6c7iwNIfC8U+Kn+9gwAQKBgQCCQqloGdRAKXc7uQgbXAOADYYmhruHDeJe6wppXRswaXWvSi1RztThDZwB1yq3eu+HC7976tipQFrtlrbDKxDoIm6DT8YJHta64l/wigujjFEA9dyfpO04GC7dkuKCiwey9AhzSYIvfirdIAfkwGWxGkUxphrWCk9Jq0vTUBICmwKBgB62djZssW11L/pZ2ninEGyCNUd7nbJZSPvfAhsS2nmGepCDwxGG82AC1r8I+/xzEWmuHBF/mjw/m8xb4r8ZoFCrIk5tzLLOWOakNLOXkazHHcPxyKiUa2ZDIniA5HJL2JUQum9uEUZrh4Xd9o9/3pVpBQJ5hlPQLPgdxje59rABAoGAbXZS1iNDOJCw+FG2YsCxUWl0fqwW41ldT6qplckCZ/oZuMNXdxCI3ZNhS1juo0A9dL4tu1wJqHdenlYlmxcVpXfz65n2mdXIULnW4eQts5qKyxY/DImJbrxi0qrRGLd0C/GMxpChjoWLrT4in87JI9PiuJT8OpNq/kgs/z3V4VQ=
Restored public key equals original: true
Restored private key equals original: true
以RSA算法为例,它的密钥有256/512/1024/2048/4096等不同的长度。长度越长,密码强度越大,当然计算速度也越慢
如果修改待加密的byte[]数据的大小,可以发现,使用512bit的RSA加密时,明文长度不能超过53字节,使用1024bit的RSA加密时,明文长度不能超过117字节,这也是为什么使用RSA的时候,总是配合AES一起使用,即用AES加密任意长度的明文,用RSA加密AES口令。
RSA算法的加密和解密操作中,密钥长度会影响可以处理的数据大小。一般来说,RSA加密操作能处理的最大数据块大小取决于密钥长度和填充模式。
在常见的填充模式下(如PKCS#1 v1.5或OAEP填充),RSA加密时需要对明文进行填充以满足算法要求的固定长度。这意味着,即使密钥长度较长,但实际能够加密的明文长度仍然受到限制 。
对于RSA加密,加密前需要对数据进行填充,以保证数据块的大小不超过密钥长度。填充的过程会增加一定的开销。一般来说,填充过程会使得实际能够加密的明文长度比密钥长度小一些。
具体来说,对于一个RSA密钥,它能够加密的最大数据块大小等于密钥长度减去一些填充的开销。因此,较短的密钥长度会限制加密的数据块大小。
在实际应用中,RSA算法的密钥长度一般选择较大的值(如2048位或更高),以提高安全性。但是,由于RSA算法加密的性能相对较慢,特别是在处理较大数据块时,因此通常不适合直接用于加密大量数据。相反,RSA常常与对称加密算法(如AES)结合使用,以提高性能和安全性。
因此,通常的做法是,使用RSA加密对称密钥(如AES密钥),然后使用对称密钥加密要传输的数据。这样既能保证安全性,又能提高加密和解密的性能。
小结
除了无法防止中间人攻击外,非对称加密算法还有一些其他缺点:
- 性能低下: 非对称加密算法的计算复杂度比对称加密算法高很多,特别是在处理大量数据时,性能会受到明显影响。因此,非对称加密算法通常用于密钥交换和数字签名等场景,而不适合直接加密大量数据。
- 密钥长度限制: 非对称加密算法的密钥长度会直接影响其安全性,通常需要选择较长的密钥长度以确保安全性。然而,较长的密钥长度会导致加密和解密的速度变慢,从而增加了计算的开销。
- 密钥管理复杂: 非对称加密算法需要管理公钥和私钥,密钥的生成、存储、分发和更新都需要一定的机制和流程来保证安全性。特别是在大规模系统中,密钥管理可能会变得非常复杂和困难。
- 安全性依赖于实现和使用: 非对称加密算法的安全性取决于其算法的设计和实现,以及密钥的生成和使用方式。如果实现存在漏洞或者密钥管理不当,可能会导致加密系统的安全性受到威胁。
综上所述,虽然非对称加密算法在密钥交换和数字签名等方面具有重要的作用,但其性能和安全性方面存在一些局限性,因此在实际应用中需要根据具体情况综合考虑,选择合适的加密方案。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。