Java加密技术(八)——数字证书

Posted on

Java加密技术(八)——数字证书

本篇的主要内容为Java证书体系的实现。![]()

请大家在阅读本篇内容时先阅读 Java加密技术(四),预先了解RSA加密算法。 在构建Java代码实现前,我们需要完成证书的制作。 1.生成keyStroe文件 在命令行下执行以下命令: Shell代码 复制代码 收藏代码

  1. keytool -genkey -validity 36000 -alias www.zlex.org -keyalg RSA -keystore d:\zlex.keystore

keytool -genkey -validity 36000 -alias www.zlex.org -keyalg RSA -keystore d:\zlex.keystore 其中 -genkey表示生成密钥 -validity指定证书有效期,这里是36000-alias指定别名,这里是www.zlex.org -keyalg指定算法,这里是RSA -keystore指定存储位置,这里是d:\zlex.keystore 在这里我使用的密码为 123456 控制台输出: Console代码 复制代码 收藏代码

  1. 输入keystore密码:
  2. 再次输入新密码:
  3. 您的名字与姓氏是什么?
  4. [Unknown]: www.zlex.org
  5. 您的组织单位名称是什么?
  6. [Unknown]: zlex
  7. 您的组织名称是什么?
  8. [Unknown]: zlex
  9. 您所在的城市或区域名称是什么?
  10. [Unknown]: BJ
  11. 您所在的州或省份名称是什么?
  12. [Unknown]: BJ
  13. 该单位的两字母国家代码是什么
  14. [Unknown]: CN
  15. CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN 正确吗?
  16. [否]: Y
  17. 输入的主密码
  18. (如果和 keystore 密码相同,按回车):
  19. 再次输入新密码:

输入keystore密码:

再次输入新密码: 您的名字与姓氏是什么?

[Unknown]: www.zlex.org 您的组织单位名称是什么?

[Unknown]: zlex 您的组织名称是什么?

[Unknown]: zlex 您所在的城市或区域名称是什么?

[Unknown]: BJ 您所在的州或省份名称是什么?

[Unknown]: BJ 该单位的两字母国家代码是什么

[Unknown]: CN CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN 正确吗?

[否]: Y

输入的主密码 (如果和 keystore 密码相同,按回车):

再次输入新密码:

这时,在D盘下会生成一个zlex.keystore的文件。 2.生成自签名证书 光有keyStore文件是不够的,还需要证书文件,证书才是直接提供给外界使用的公钥凭证。 导出证书: Shell代码 复制代码 收藏代码

  1. keytool -export -keystore d:\zlex.keystore -alias www.zlex.org -file d:\zlex.cer -rfc

keytool -export -keystore d:\zlex.keystore -alias www.zlex.org -file d:\zlex.cer -rfc 其中 -export指定为导出操作 -keystore指定keystore文件 -alias指定导出keystore文件中的别名 -file指向导出路径 -rfc以文本格式输出,也就是以BASE64编码输出 这里的密码是 123456 控制台输出: Console代码 复制代码 收藏代码

  1. 输入keystore密码:
  2. 保存在文件中的认证

输入keystore密码:

保存在文件中的认证 当然,使用方是需要导入证书的! 可以通过自签名证书完成CAS单点登录系统的构建! Ok,准备工作完成,开始Java实现! 通过java代码实现如下:Coder类见 Java加密技术(一) Java代码 复制代码 收藏代码

  1. import java.io.FileInputStream;
  2. import java.security.KeyStore;
  3. import java.security.PrivateKey;
  4. import java.security.PublicKey;
  5. import java.security.Signature;
  6. import java.security.cert.Certificate;
  7. import java.security.cert.CertificateFactory;
  8. import java.security.cert.X509Certificate;
  9. import java.util.Date;
  10. import javax.crypto.Cipher;
  11. ///
  12. /* 证书组件
  13. /*
  14. /* @author 梁栋
  15. /* @version 1.0
  16. /* @since 1.0
  17. /*/
  18. public abstract class CertificateCoder extends Coder {
  19. ///
  20. /* Java密钥库(Java Key Store,JKS)KEY_STORE
  21. /*/
  22. public static final String KEY_STORE = "JKS";
  23. public static final String X509 = "X.509";
  24. ///
  25. /* 由KeyStore获得私钥
  26. /*
  27. /* @param keyStorePath
  28. /* @param alias
  29. /* @param password
  30. /* @return
  31. /* @throws Exception
  32. /*/
  33. private static PrivateKey getPrivateKey(String keyStorePath, String alias,
  34. String password) throws Exception {
  35. KeyStore ks = getKeyStore(keyStorePath, password);
  36. PrivateKey key = (PrivateKey) ks.getKey(alias, password.toCharArray());
  37. return key;
  38. }
  39. ///
  40. /* 由Certificate获得公钥
  41. /*
  42. /* @param certificatePath
  43. /* @return
  44. /* @throws Exception
  45. /*/
  46. private static PublicKey getPublicKey(String certificatePath)
  47. throws Exception {
  48. Certificate certificate = getCertificate(certificatePath);
  49. PublicKey key = certificate.getPublicKey();
  50. return key;
  51. }
  52. ///
  53. /* 获得Certificate
  54. /*
  55. /* @param certificatePath
  56. /* @return
  57. /* @throws Exception
  58. /*/
  59. private static Certificate getCertificate(String certificatePath)
  60. throws Exception {
  61. CertificateFactory certificateFactory = CertificateFactory
  62. .getInstance(X509);
  63. FileInputStream in = new FileInputStream(certificatePath);
  64. Certificate certificate = certificateFactory.generateCertificate(in);
  65. in.close();
  66. return certificate;
  67. }
  68. ///
  69. /* 获得Certificate
  70. /*
  71. /* @param keyStorePath
  72. /* @param alias
  73. /* @param password
  74. /* @return
  75. /* @throws Exception
  76. /*/
  77. private static Certificate getCertificate(String keyStorePath,
  78. String alias, String password) throws Exception {
  79. KeyStore ks = getKeyStore(keyStorePath, password);
  80. Certificate certificate = ks.getCertificate(alias);
  81. return certificate;
  82. }
  83. ///
  84. /* 获得KeyStore
  85. /*
  86. /* @param keyStorePath
  87. /* @param password
  88. /* @return
  89. /* @throws Exception
  90. /*/
  91. private static KeyStore getKeyStore(String keyStorePath, String password)
  92. throws Exception {
  93. FileInputStream is = new FileInputStream(keyStorePath);
  94. KeyStore ks = KeyStore.getInstance(KEY_STORE);
  95. ks.load(is, password.toCharArray());
  96. is.close();
  97. return ks;
  98. }
  99. ///
  100. /* 私钥加密
  101. /*
  102. /* @param data
  103. /* @param keyStorePath
  104. /* @param alias
  105. /* @param password
  106. /* @return
  107. /* @throws Exception
  108. /*/
  109. public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,
  110. String alias, String password) throws Exception {
  111. // 取得私钥
  112. PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);
  113. // 对数据加密
  114. Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
  115. cipher.init(Cipher.ENCRYPT_MODE, privateKey);
  116. return cipher.doFinal(data);
  117. }
  118. ///
  119. /* 私钥解密
  120. /*
  121. /* @param data
  122. /* @param keyStorePath
  123. /* @param alias
  124. /* @param password
  125. /* @return
  126. /* @throws Exception
  127. /*/
  128. public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,
  129. String alias, String password) throws Exception {
  130. // 取得私钥
  131. PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);
  132. // 对数据加密
  133. Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
  134. cipher.init(Cipher.DECRYPT_MODE, privateKey);
  135. return cipher.doFinal(data);
  136. }
  137. ///
  138. /* 公钥加密
  139. /*
  140. /* @param data
  141. /* @param certificatePath
  142. /* @return
  143. /* @throws Exception
  144. /*/
  145. public static byte[] encryptByPublicKey(byte[] data, String certificatePath)
  146. throws Exception {
  147. // 取得公钥
  148. PublicKey publicKey = getPublicKey(certificatePath);
  149. // 对数据加密
  150. Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
  151. cipher.init(Cipher.ENCRYPT_MODE, publicKey);
  152. return cipher.doFinal(data);
  153. }
  154. ///
  155. /* 公钥解密
  156. /*
  157. /* @param data
  158. /* @param certificatePath
  159. /* @return
  160. /* @throws Exception
  161. /*/
  162. public static byte[] decryptByPublicKey(byte[] data, String certificatePath)
  163. throws Exception {
  164. // 取得公钥
  165. PublicKey publicKey = getPublicKey(certificatePath);
  166. // 对数据加密
  167. Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
  168. cipher.init(Cipher.DECRYPT_MODE, publicKey);
  169. return cipher.doFinal(data);
  170. }
  171. ///
  172. /* 验证Certificate
  173. /*
  174. /* @param certificatePath
  175. /* @return
  176. /*/
  177. public static boolean verifyCertificate(String certificatePath) {
  178. return verifyCertificate(new Date(), certificatePath);
  179. }
  180. ///
  181. /* 验证Certificate是否过期或无效
  182. /*
  183. /* @param date
  184. /* @param certificatePath
  185. /* @return
  186. /*/
  187. public static boolean verifyCertificate(Date date, String certificatePath) {
  188. boolean status = true;
  189. try {
  190. // 取得证书
  191. Certificate certificate = getCertificate(certificatePath);
  192. // 验证证书是否过期或无效
  193. status = verifyCertificate(date, certificate);
  194. } catch (Exception e) {
  195. status = false;
  196. }
  197. return status;
  198. }
  199. ///
  200. /* 验证证书是否过期或无效
  201. /*
  202. /* @param date
  203. /* @param certificate
  204. /* @return
  205. /*/
  206. private static boolean verifyCertificate(Date date, Certificate certificate) {
  207. boolean status = true;
  208. try {
  209. X509Certificate x509Certificate = (X509Certificate) certificate;
  210. x509Certificate.checkValidity(date);
  211. } catch (Exception e) {
  212. status = false;
  213. }
  214. return status;
  215. }
  216. ///
  217. /* 签名
  218. /*
  219. /* @param keyStorePath
  220. /* @param alias
  221. /* @param password
  222. /*
  223. /* @return
  224. /* @throws Exception
  225. /*/
  226. public static String sign(byte[] sign, String keyStorePath, String alias,
  227. String password) throws Exception {
  228. // 获得证书
  229. X509Certificate x509Certificate = (X509Certificate) getCertificate(
  230. keyStorePath, alias, password);
  231. // 获取私钥
  232. KeyStore ks = getKeyStore(keyStorePath, password);
  233. // 取得私钥
  234. PrivateKey privateKey = (PrivateKey) ks.getKey(alias, password
  235. .toCharArray());
  236. // 构建签名
  237. Signature signature = Signature.getInstance(x509Certificate
  238. .getSigAlgName());
  239. signature.initSign(privateKey);
  240. signature.update(sign);
  241. return encryptBASE64(signature.sign());
  242. }
  243. ///
  244. /* 验证签名
  245. /*
  246. /* @param data
  247. /* @param sign
  248. /* @param certificatePath
  249. /* @return
  250. /* @throws Exception
  251. /*/
  252. public static boolean verify(byte[] data, String sign,
  253. String certificatePath) throws Exception {
  254. // 获得证书
  255. X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);
  256. // 获得公钥
  257. PublicKey publicKey = x509Certificate.getPublicKey();
  258. // 构建签名
  259. Signature signature = Signature.getInstance(x509Certificate
  260. .getSigAlgName());
  261. signature.initVerify(publicKey);
  262. signature.update(data);
  263. return signature.verify(decryptBASE64(sign));
  264. }
  265. ///
  266. /* 验证Certificate
  267. /*
  268. /* @param keyStorePath
  269. /* @param alias
  270. /* @param password
  271. /* @return
  272. /*/
  273. public static boolean verifyCertificate(Date date, String keyStorePath,
  274. String alias, String password) {
  275. boolean status = true;
  276. try {
  277. Certificate certificate = getCertificate(keyStorePath, alias,
  278. password);
  279. status = verifyCertificate(date, certificate);
  280. } catch (Exception e) {
  281. status = false;
  282. }
  283. return status;
  284. }
  285. ///
  286. /* 验证Certificate
  287. /*
  288. /* @param keyStorePath
  289. /* @param alias
  290. /* @param password
  291. /* @return
  292. /*/
  293. public static boolean verifyCertificate(String keyStorePath, String alias,
  294. String password) {
  295. return verifyCertificate(new Date(), keyStorePath, alias, password);
  296. }
  297. }

import java.io.FileInputStream;

import java.security.KeyStore; import java.security.PrivateKey;

import java.security.PublicKey; import java.security.Signature;

import java.security.cert.Certificate; import java.security.cert.CertificateFactory;

import java.security.cert.X509Certificate; import java.util.Date;

import javax.crypto.Cipher;

///

/ 证书组件 /

/ @author 梁栋 / @version 1.0

/ @since 1.0 //

public abstract class CertificateCoder extends Coder {

//*/*

 /* Java密钥库(Java Key Store,JKS)KEY_STORE
 /*/

public static final String KEY_STORE = "JKS";


public static final String X509 = "X.509";


//*/*
 /* 由KeyStore获得私钥

 /*
 /* @param keyStorePath

 /* @param alias
 /* @param password

 /* @return
 /* @throws Exception

 /*/
private static PrivateKey getPrivateKey(String keyStorePath, String alias,

        String password) throws Exception {
    KeyStore ks = getKeyStore(keyStorePath, password);

    PrivateKey key = (PrivateKey) ks.getKey(alias, password.toCharArray());
    return key;

}


//*/*
 /* 由Certificate获得公钥

 /*
 /* @param certificatePath

 /* @return
 /* @throws Exception

 /*/
private static PublicKey getPublicKey(String certificatePath)

        throws Exception {
    Certificate certificate = getCertificate(certificatePath);

    PublicKey key = certificate.getPublicKey();
    return key;

}


//*/*
 /* 获得Certificate

 /*
 /* @param certificatePath

 /* @return
 /* @throws Exception

 /*/
private static Certificate getCertificate(String certificatePath)

        throws Exception {
    CertificateFactory certificateFactory = CertificateFactory

            .getInstance(X509);
    FileInputStream in = new FileInputStream(certificatePath);


    Certificate certificate = certificateFactory.generateCertificate(in);

    in.close();


    return certificate;
}


//*/*

 /* 获得Certificate
 /*

 /* @param keyStorePath
 /* @param alias

 /* @param password
 /* @return

 /* @throws Exception
 /*/

private static Certificate getCertificate(String keyStorePath,
        String alias, String password) throws Exception {

    KeyStore ks = getKeyStore(keyStorePath, password);
    Certificate certificate = ks.getCertificate(alias);


    return certificate;

}


//*/*
 /* 获得KeyStore

 /*
 /* @param keyStorePath

 /* @param password
 /* @return

 /* @throws Exception
 /*/

private static KeyStore getKeyStore(String keyStorePath, String password)
        throws Exception {

    FileInputStream is = new FileInputStream(keyStorePath);
    KeyStore ks = KeyStore.getInstance(KEY_STORE);

    ks.load(is, password.toCharArray());
    is.close();

    return ks;
}


//*/*

 /* 私钥加密
 /*

 /* @param data
 /* @param keyStorePath

 /* @param alias
 /* @param password

 /* @return
 /* @throws Exception

 /*/
public static byte[] encryptByPrivateKey(byte[] data, String keyStorePath,

        String alias, String password) throws Exception {
    // 取得私钥

    PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);


    // 对数据加密
    Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());

    cipher.init(Cipher.ENCRYPT_MODE, privateKey);


    return cipher.doFinal(data);


}


//*/*
 /* 私钥解密

 /*
 /* @param data

 /* @param keyStorePath
 /* @param alias

 /* @param password
 /* @return

 /* @throws Exception
 /*/

public static byte[] decryptByPrivateKey(byte[] data, String keyStorePath,
        String alias, String password) throws Exception {

    // 取得私钥
    PrivateKey privateKey = getPrivateKey(keyStorePath, alias, password);


    // 对数据加密

    Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm());
    cipher.init(Cipher.DECRYPT_MODE, privateKey);


    return cipher.doFinal(data);


}


//*/*

 /* 公钥加密
 /*

 /* @param data
 /* @param certificatePath

 /* @return
 /* @throws Exception

 /*/
public static byte[] encryptByPublicKey(byte[] data, String certificatePath)

        throws Exception {


    // 取得公钥
    PublicKey publicKey = getPublicKey(certificatePath);

    // 对数据加密
    Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());

    cipher.init(Cipher.ENCRYPT_MODE, publicKey);


    return cipher.doFinal(data);


}


//*/*
 /* 公钥解密

 /*
 /* @param data

 /* @param certificatePath
 /* @return

 /* @throws Exception
 /*/

public static byte[] decryptByPublicKey(byte[] data, String certificatePath)
        throws Exception {

    // 取得公钥
    PublicKey publicKey = getPublicKey(certificatePath);


    // 对数据加密

    Cipher cipher = Cipher.getInstance(publicKey.getAlgorithm());
    cipher.init(Cipher.DECRYPT_MODE, publicKey);


    return cipher.doFinal(data);


}


//*/*

 /* 验证Certificate
 /*

 /* @param certificatePath
 /* @return

 /*/
public static boolean verifyCertificate(String certificatePath) {

    return verifyCertificate(new Date(), certificatePath);
}


//*/*

 /* 验证Certificate是否过期或无效
 /*

 /* @param date
 /* @param certificatePath

 /* @return
 /*/

public static boolean verifyCertificate(Date date, String certificatePath) {
    boolean status = true;

    try {
        // 取得证书

        Certificate certificate = getCertificate(certificatePath);
        // 验证证书是否过期或无效

        status = verifyCertificate(date, certificate);
    } catch (Exception e) {

        status = false;
    }

    return status;
}


//*/*

 /* 验证证书是否过期或无效
 /*

 /* @param date
 /* @param certificate

 /* @return
 /*/

private static boolean verifyCertificate(Date date, Certificate certificate) {
    boolean status = true;

    try {
        X509Certificate x509Certificate = (X509Certificate) certificate;

        x509Certificate.checkValidity(date);
    } catch (Exception e) {

        status = false;
    }

    return status;
}


//*/*

 /* 签名
 /*

 /* @param keyStorePath
 /* @param alias

 /* @param password
 /*

 /* @return
 /* @throws Exception

 /*/
public static String sign(byte[] sign, String keyStorePath, String alias,

        String password) throws Exception {
    // 获得证书

    X509Certificate x509Certificate = (X509Certificate) getCertificate(
            keyStorePath, alias, password);

    // 获取私钥
    KeyStore ks = getKeyStore(keyStorePath, password);

    // 取得私钥
    PrivateKey privateKey = (PrivateKey) ks.getKey(alias, password

            .toCharArray());


    // 构建签名
    Signature signature = Signature.getInstance(x509Certificate

            .getSigAlgName());
    signature.initSign(privateKey);

    signature.update(sign);
    return encryptBASE64(signature.sign());

}


//*/*
 /* 验证签名

 /*
 /* @param data

 /* @param sign
 /* @param certificatePath

 /* @return
 /* @throws Exception

 /*/
public static boolean verify(byte[] data, String sign,

        String certificatePath) throws Exception {
    // 获得证书

    X509Certificate x509Certificate = (X509Certificate) getCertificate(certificatePath);
    // 获得公钥

    PublicKey publicKey = x509Certificate.getPublicKey();
    // 构建签名

    Signature signature = Signature.getInstance(x509Certificate
            .getSigAlgName());

    signature.initVerify(publicKey);
    signature.update(data);


    return signature.verify(decryptBASE64(sign));


}


//*/*

 /* 验证Certificate
 /*

 /* @param keyStorePath
 /* @param alias

 /* @param password
 /* @return

 /*/
public static boolean verifyCertificate(Date date, String keyStorePath,

        String alias, String password) {
    boolean status = true;

    try {
        Certificate certificate = getCertificate(keyStorePath, alias,

                password);
        status = verifyCertificate(date, certificate);

    } catch (Exception e) {
        status = false;

    }
    return status;

}


//*/*
 /* 验证Certificate

 /*
 /* @param keyStorePath

 /* @param alias
 /* @param password

 /* @return
 /*/

public static boolean verifyCertificate(String keyStorePath, String alias,
        String password) {

    return verifyCertificate(new Date(), keyStorePath, alias, password);
}

} 再给出一个测试类: Java代码 复制代码 收藏代码

  1. import static org.junit.Assert./*;
  2. import org.junit.Test;
  3. ///
  4. /*
  5. /* @author 梁栋
  6. /* @version 1.0
  7. /* @since 1.0
  8. /*/
  9. public class CertificateCoderTest {
  10. private String password = "123456";
  11. private String alias = "www.zlex.org";
  12. private String certificatePath = "d:/zlex.cer";
  13. private String keyStorePath = "d:/zlex.keystore";
  14. @Test
  15. public void test() throws Exception {
  16. System.err.println("公钥加密——私钥解密");
  17. String inputStr = "Ceritifcate";
  18. byte[] data = inputStr.getBytes();
  19. byte[] encrypt = CertificateCoder.encryptByPublicKey(data,
  20. certificatePath);
  21. byte[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt,
  22. keyStorePath, alias, password);
  23. String outputStr = new String(decrypt);
  24. System.err.println("加密前: " + inputStr + "\n\r" + "解密后: " + outputStr);
  25. // 验证数据一致
  26. assertArrayEquals(data, decrypt);
  27. // 验证证书有效
  28. assertTrue(CertificateCoder.verifyCertificate(certificatePath));
  29. }
  30. @Test
  31. public void testSign() throws Exception {
  32. System.err.println("私钥加密——公钥解密");
  33. String inputStr = "sign";
  34. byte[] data = inputStr.getBytes();
  35. byte[] encodedData = CertificateCoder.encryptByPrivateKey(data,
  36. keyStorePath, alias, password);
  37. byte[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,
  38. certificatePath);
  39. String outputStr = new String(decodedData);
  40. System.err.println("加密前: " + inputStr + "\n\r" + "解密后: " + outputStr);
  41. assertEquals(inputStr, outputStr);
  42. System.err.println("私钥签名——公钥验证签名");
  43. // 产生签名
  44. String sign = CertificateCoder.sign(encodedData, keyStorePath, alias,
  45. password);
  46. System.err.println("签名:\r" + sign);
  47. // 验证签名
  48. boolean status = CertificateCoder.verify(encodedData, sign,
  49. certificatePath);
  50. System.err.println("状态:\r" + status);
  51. assertTrue(status);
  52. }
  53. }

import static org.junit.Assert./*;

import org.junit.Test;

///

/ / @author 梁栋

/ @version 1.0 / @since 1.0

/*/ public class CertificateCoderTest {

private String password = "123456";
private String alias = "www.zlex.org";

private String certificatePath = "d:/zlex.cer";
private String keyStorePath = "d:/zlex.keystore";


@Test

public void test() throws Exception {
    System.err.println("公钥加密——私钥解密");

    String inputStr = "Ceritifcate";
    byte[] data = inputStr.getBytes();


    byte[] encrypt = CertificateCoder.encryptByPublicKey(data,

            certificatePath);


    byte[] decrypt = CertificateCoder.decryptByPrivateKey(encrypt,
            keyStorePath, alias, password);

    String outputStr = new String(decrypt);


    System.err.println("加密前: " + inputStr + "\n\r" + "解密后: " + outputStr);


    // 验证数据一致
    assertArrayEquals(data, decrypt);


    // 验证证书有效

    assertTrue(CertificateCoder.verifyCertificate(certificatePath));


}


@Test
public void testSign() throws Exception {

    System.err.println("私钥加密——公钥解密");


    String inputStr = "sign";
    byte[] data = inputStr.getBytes();


    byte[] encodedData = CertificateCoder.encryptByPrivateKey(data,

            keyStorePath, alias, password);


    byte[] decodedData = CertificateCoder.decryptByPublicKey(encodedData,
            certificatePath);


    String outputStr = new String(decodedData);

    System.err.println("加密前: " + inputStr + "\n\r" + "解密后: " + outputStr);
    assertEquals(inputStr, outputStr);


    System.err.println("私钥签名——公钥验证签名");

    // 产生签名
    String sign = CertificateCoder.sign(encodedData, keyStorePath, alias,

            password);
    System.err.println("签名:\r" + sign);


    // 验证签名

    boolean status = CertificateCoder.verify(encodedData, sign,
            certificatePath);

    System.err.println("状态:\r" + status);
    assertTrue(status);


}

} 控制台输出: Console代码 复制代码 收藏代码

  1. 公钥加密——私钥解密
  2. 加密前: Ceritificate
  3. 解密后: Ceritificate
  4. 私钥加密——公钥解密
  5. 加密前: sign
  6. 解密后: sign
  7. 私钥签名——公钥验证签名
  8. 签名:
  9. pqBn5m6PJlfOjH0A6U2o2mUmBsfgyEY1NWCbiyA/I5Gc3gaVNVIdj/zkGNZRqTjhf3+J9a9z9EI7
  10. 6F2eWYd7punHx5oh6hfNgcKbVb52EfItl4QEN+djbXiPynn07+Lbg1NOjULnpEd6ZhLP1YwrEAuM
  11. OfvX0e7/wplxLbySaKQ=
  12. 状态:
  13. true

公钥加密——私钥解密

加密前: Ceritificate

解密后: Ceritificate

私钥加密——公钥解密 加密前: sign

解密后: sign

私钥签名——公钥验证签名 签名:

pqBn5m6PJlfOjH0A6U2o2mUmBsfgyEY1NWCbiyA/I5Gc3gaVNVIdj/zkGNZRqTjhf3+J9a9z9EI7 6F2eWYd7punHx5oh6hfNgcKbVb52EfItl4QEN+djbXiPynn07+Lbg1NOjULnpEd6ZhLP1YwrEAuM

OfvX0e7/wplxLbySaKQ=

状态: true 由此完成了证书验证体系! 同样,我们可以对代码做签名——代码签名! 通过工具JarSigner可以完成代码签名。 这里我们对tools.jar做代码签名,命令如下: Shell代码 复制代码 收藏代码

  1. jarsigner -storetype jks -keystore zlex.keystore -verbose tools.jar www.zlex.org

jarsigner -storetype jks -keystore zlex.keystore -verbose tools.jar www.zlex.org 控制台输出: Console代码 复制代码 收藏代码

  1. 输入密钥库的口令短语:
  2. 正在更新: META-INF/WWW_ZLEX.SF
  3. 正在更新: META-INF/WWW_ZLEX.RSA
  4. 正在签名: org/zlex/security/Security.class
  5. 正在签名: org/zlex/tool/Main$1.class
  6. 正在签名: org/zlex/tool/Main$2.class
  7. 正在签名: org/zlex/tool/Main.class
  8. 警告:
  9. 签名者证书将在六个月内过期。

输入密钥库的口令短语:

正在更新: META-INF/WWW_ZLEX.SF 正在更新: META-INF/WWW_ZLEX.RSA

正在签名: org/zlex/security/Security.class 正在签名: org/zlex/tool/Main$1.class

正在签名: org/zlex/tool/Main$2.class 正在签名: org/zlex/tool/Main.class

警告:

签名者证书将在六个月内过期。 此时,我们可以对签名后的jar做验证! 验证tools.jar,命令如下: Shell代码 复制代码 收藏代码

  1. jarsigner -verify -verbose -certs tools.jar

jarsigner -verify -verbose -certs tools.jar 控制台输出: Console代码 复制代码 收藏代码

  1. 402 Sat Jun 20 16:25:14 CST 2009 META-INF/MANIFEST.MF
  2. 532 Sat Jun 20 16:25:14 CST 2009 META-INF/WWW_ZLEX.SF
  3. 889 Sat Jun 20 16:25:14 CST 2009 META-INF/WWW_ZLEX.RSA
  4. sm 590 Wed Dec 10 13:03:42 CST 2008 org/zlex/security/Security.class
  5. X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN
  6. [证书将在 09-9-18 下午3:27 到期]
  7. sm 705 Tue Dec 16 18:00:56 CST 2008 org/zlex/tool/Main$1.class
  8. X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN
  9. [证书将在 09-9-18 下午3:27 到期]
  10. sm 779 Tue Dec 16 18:00:56 CST 2008 org/zlex/tool/Main$2.class
  11. X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN
  12. [证书将在 09-9-18 下午3:27 到期]
  13. sm 12672 Tue Dec 16 18:00:56 CST 2008 org/zlex/tool/Main.class
  14. X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN
  15. [证书将在 09-9-18 下午3:27 到期]
  16. s = 已验证签名
  17. m = 在清单中列出条目
  18. k = 在密钥库中至少找到了一个证书
  19. i = 在身份作用域内至少找到了一个证书
  20. jar 已验证。
  21. 警告:
  22. 此 jar 包含签名者证书将在六个月内过期的条目。

      402 Sat Jun 20 16:25:14 CST 2009 META-INF/MANIFEST.MF
    
      532 Sat Jun 20 16:25:14 CST 2009 META-INF/WWW_ZLEX.SF
      889 Sat Jun 20 16:25:14 CST 2009 META-INF/WWW_ZLEX.RSA
    

sm 590 Wed Dec 10 13:03:42 CST 2008 org/zlex/security/Security.class

  X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN
  [证书将在 09-9-18 下午3:27 到期]

sm 705 Tue Dec 16 18:00:56 CST 2008 org/zlex/tool/Main$1.class

  X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN

  [证书将在 09-9-18 下午3:27 到期]

sm 779 Tue Dec 16 18:00:56 CST 2008 org/zlex/tool/Main$2.class

  X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN
  [证书将在 09-9-18 下午3:27 到期]

sm 12672 Tue Dec 16 18:00:56 CST 2008 org/zlex/tool/Main.class

  X.509, CN=www.zlex.org, OU=zlex, O=zlex, L=BJ, ST=BJ, C=CN

  [证书将在 09-9-18 下午3:27 到期]

s = 已验证签名

m = 在清单中列出条目 k = 在密钥库中至少找到了一个证书

i = 在身份作用域内至少找到了一个证书

jar 已验证。

警告: 此 jar 包含签名者证书将在六个月内过期的条目。 代码签名认证的用途主要是对发布的软件做验证,支持 Sun Java .jar (Java Applet) 文件(J2SE)和 J2ME MIDlet Suite 文件。 相关链接: Java加密技术(一)——BASE64与单向加密算法MD5&SHA&MAC Java加密技术(二)——对称加密DES&AES Java加密技术(三)——PBE算法 Java加密技术(四)——非对称加密算法RSA Java加密技术(五)——非对称加密算法的由来 Java加密技术(六)——数字签名算法DSA Java加密技术(七)——非对称加密算法最高ECC Java加密技术(八)——数字证书 Java加密技术(九)——初探SSL Java加密技术(十)——单向认证 Java加密技术(十一)——双向认证 Java加密技术(十二)——/.PFX(/.p12)&个人信息交换文件

Java加密技术(二)——对称加密算法DES&AES

Posted on

Java加密技术(二)——对称加密算法DES&AES

接下来我们介绍对称加密算法,最常用的莫过于DES数据加密算法。

DES DES-Data Encryption Standard,即数据加密算法。是IBM公司于1975年研究成功并公开发表的。DES算法的入口参数有三个:Key、Data、Mode。其中Key为8个字节共64位,是DES算法的工作密钥;Data也为8个字节64位,是要被加密或被解密的数据;Mode为DES的工作方式,有两种:加密或解密。 DES算法把64位的明文输入块变为64位的密文输出块,它所使用的密钥也是64位。 通过java代码实现如下:Coder类见 Java加密技术(一) Java代码 复制代码 收藏代码

  1. import java.security.Key;
  2. import java.security.SecureRandom;
  3. import javax.crypto.Cipher;
  4. import javax.crypto.KeyGenerator;
  5. import javax.crypto.SecretKey;
  6. import javax.crypto.SecretKeyFactory;
  7. import javax.crypto.spec.DESKeySpec;
  8. ///
  9. /* DES安全编码组件
  10. /*
  11. /*
     
  12. /* 支持 DES、DESede(TripleDES,就是3DES)、AES、Blowfish、RC2、RC4(ARCFOUR)
  13. /* DES key size must be equal to 56
  14. /* DESede(TripleDES) key size must be equal to 112 or 168
  15. /* AES key size must be equal to 128, 192 or 256,but 192 and 256 bits may not be available
  16. /* Blowfish key size must be multiple of 8, and can only range from 32 to 448 (inclusive)
  17. /* RC2 key size must be between 40 and 1024 bits
  18. /* RC4(ARCFOUR) key size must be between 40 and 1024 bits
  19. /* 具体内容 需要关注 JDK Document http://.../docs/technotes/guides/security/SunProviders.html
  20. /*
  21. /*
  22. /* @author 梁栋
  23. /* @version 1.0
  24. /* @since 1.0
  25. /*/
  26. public abstract class DESCoder extends Coder {
  27. ///
  28. /* ALGORITHM 算法
  29. /* 可替换为以下任意一种算法,同时key值的size相应改变。
  30. /*
  31. /*
     
  32. /* DES key size must be equal to 56
  33. /* DESede(TripleDES) key size must be equal to 112 or 168
  34. /* AES key size must be equal to 128, 192 or 256,but 192 and 256 bits may not be available
  35. /* Blowfish key size must be multiple of 8, and can only range from 32 to 448 (inclusive)
  36. /* RC2 key size must be between 40 and 1024 bits
  37. /* RC4(ARCFOUR) key size must be between 40 and 1024 bits
  38. /*
  39. /*
  40. /* 在Key toKey(byte[] key)方法中使用下述代码
  41. /* SecretKey secretKey = new SecretKeySpec(key, ALGORITHM); 替换
  42. /*
  43. /* DESKeySpec dks = new DESKeySpec(key);
  44. /* SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
  45. /* SecretKey secretKey = keyFactory.generateSecret(dks);
  46. /*
  47. /*/
  48. public static final String ALGORITHM = "DES";
  49. ///
  50. /* 转换密钥
  51. /*
  52. /* @param key
  53. /* @return
  54. /* @throws Exception
  55. /*/
  56. private static Key toKey(byte[] key) throws Exception {
  57. DESKeySpec dks = new DESKeySpec(key);
  58. SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
  59. SecretKey secretKey = keyFactory.generateSecret(dks);
  60. // 当使用其他对称加密算法时,如AES、Blowfish等算法时,用下述代码替换上述三行代码
  61. // SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);
  62. return secretKey;
  63. }
  64. ///
  65. /* 解密
  66. /*
  67. /* @param data
  68. /* @param key
  69. /* @return
  70. /* @throws Exception
  71. /*/
  72. public static byte[] decrypt(byte[] data, String key) throws Exception {
  73. Key k = toKey(decryptBASE64(key));
  74. Cipher cipher = Cipher.getInstance(ALGORITHM);
  75. cipher.init(Cipher.DECRYPT_MODE, k);
  76. return cipher.doFinal(data);
  77. }
  78. ///
  79. /* 加密
  80. /*
  81. /* @param data
  82. /* @param key
  83. /* @return
  84. /* @throws Exception
  85. /*/
  86. public static byte[] encrypt(byte[] data, String key) throws Exception {
  87. Key k = toKey(decryptBASE64(key));
  88. Cipher cipher = Cipher.getInstance(ALGORITHM);
  89. cipher.init(Cipher.ENCRYPT_MODE, k);
  90. return cipher.doFinal(data);
  91. }
  92. ///
  93. /* 生成密钥
  94. /*
  95. /* @return
  96. /* @throws Exception
  97. /*/
  98. public static String initKey() throws Exception {
  99. return initKey(null);
  100. }
  101. ///
  102. /* 生成密钥
  103. /*
  104. /* @param seed
  105. /* @return
  106. /* @throws Exception
  107. /*/
  108. public static String initKey(String seed) throws Exception {
  109. SecureRandom secureRandom = null;
  110. if (seed != null) {
  111. secureRandom = new SecureRandom(decryptBASE64(seed));
  112. } else {
  113. secureRandom = new SecureRandom();
  114. }
  115. KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM);
  116. kg.init(secureRandom);
  117. SecretKey secretKey = kg.generateKey();
  118. return encryptBASE64(secretKey.getEncoded());
  119. }
  120. }

import java.security.Key;

import java.security.SecureRandom;

import javax.crypto.Cipher; import javax.crypto.KeyGenerator;

import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory;

import javax.crypto.spec.DESKeySpec;

///

/ DES安全编码组件 /

/

/ 支持 DES、DESede(TripleDES,就是3DES)、AES、Blowfish、RC2、RC4(ARCFOUR)

/ DES key size must be equal to 56 / DESede(TripleDES) key size must be equal to 112 or 168

/ AES key size must be equal to 128, 192 or 256,but 192 and 256 bits may not be available / Blowfish key size must be multiple of 8, and can only range from 32 to 448 (inclusive)

/ RC2 key size must be between 40 and 1024 bits / RC4(ARCFOUR) key size must be between 40 and 1024 bits

/ 具体内容 需要关注 JDK Document http://.../docs/technotes/guides/security/SunProviders.html /

/ / @author 梁栋

/ @version 1.0 / @since 1.0

/*/ public abstract class DESCoder extends Coder {

//*/*
 /* ALGORITHM 算法 <br>

 /* 可替换为以下任意一种算法,同时key值的size相应改变。
 /*

 /* <pre>
 /* DES                  key size must be equal to 56

 /* DESede(TripleDES)     key size must be equal to 112 or 168
 /* AES                  key size must be equal to 128, 192 or 256,but 192 and 256 bits may not be available

 /* Blowfish             key size must be multiple of 8, and can only range from 32 to 448 (inclusive)
 /* RC2                  key size must be between 40 and 1024 bits

 /* RC4(ARCFOUR)         key size must be between 40 and 1024 bits
 /* </pre>

 /*
 /* 在Key toKey(byte[] key)方法中使用下述代码

 /* <code>SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);</code> 替换


 /* <code>
 /* DESKeySpec dks = new DESKeySpec(key);

 /* SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);
 /* SecretKey secretKey = keyFactory.generateSecret(dks);

 /* </code>
 /*/

public static final String ALGORITHM = "DES";


//*/*
 /* 转换密钥<br>

 /*
 /* @param key

 /* @return
 /* @throws Exception

 /*/
private static Key toKey(byte[] key) throws Exception {

    DESKeySpec dks = new DESKeySpec(key);
    SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(ALGORITHM);

    SecretKey secretKey = keyFactory.generateSecret(dks);


    // 当使用其他对称加密算法时,如AES、Blowfish等算法时,用下述代码替换上述三行代码
    // SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);


    return secretKey;

}


//*/*
 /* 解密

 /*
 /* @param data

 /* @param key
 /* @return

 /* @throws Exception
 /*/

public static byte[] decrypt(byte[] data, String key) throws Exception {
    Key k = toKey(decryptBASE64(key));


    Cipher cipher = Cipher.getInstance(ALGORITHM);

    cipher.init(Cipher.DECRYPT_MODE, k);


    return cipher.doFinal(data);
}


//*/*

 /* 加密
 /*

 /* @param data
 /* @param key

 /* @return
 /* @throws Exception

 /*/
public static byte[] encrypt(byte[] data, String key) throws Exception {

    Key k = toKey(decryptBASE64(key));
    Cipher cipher = Cipher.getInstance(ALGORITHM);

    cipher.init(Cipher.ENCRYPT_MODE, k);


    return cipher.doFinal(data);
}


//*/*

 /* 生成密钥
 /*

 /* @return
 /* @throws Exception

 /*/
public static String initKey() throws Exception {

    return initKey(null);
}


//*/*

 /* 生成密钥
 /*

 /* @param seed
 /* @return

 /* @throws Exception
 /*/

public static String initKey(String seed) throws Exception {
    SecureRandom secureRandom = null;


    if (seed != null) {

        secureRandom = new SecureRandom(decryptBASE64(seed));
    } else {

        secureRandom = new SecureRandom();
    }


    KeyGenerator kg = KeyGenerator.getInstance(ALGORITHM);

    kg.init(secureRandom);


    SecretKey secretKey = kg.generateKey();


    return encryptBASE64(secretKey.getEncoded());
}

} 延续上一个类的实现,我们通过MD5以及SHA对字符串加密生成密钥,这是比较常见的密钥生成方式。 再给出一个测试类: Java代码 复制代码 收藏代码

  1. import static org.junit.Assert./*;
  2. import org.junit.Test;
  3. ///
  4. /*
  5. /* @author 梁栋
  6. /* @version 1.0
  7. /* @since 1.0
  8. /*/
  9. public class DESCoderTest {
  10. @Test
  11. public void test() throws Exception {
  12. String inputStr = "DES";
  13. String key = DESCoder.initKey();
  14. System.err.println("原文:\t" + inputStr);
  15. System.err.println("密钥:\t" + key);
  16. byte[] inputData = inputStr.getBytes();
  17. inputData = DESCoder.encrypt(inputData, key);
  18. System.err.println("加密后:\t" + DESCoder.encryptBASE64(inputData));
  19. byte[] outputData = DESCoder.decrypt(inputData, key);
  20. String outputStr = new String(outputData);
  21. System.err.println("解密后:\t" + outputStr);
  22. assertEquals(inputStr, outputStr);
  23. }
  24. }

import static org.junit.Assert./*;

import org.junit.Test;

/// /*

/ @author 梁栋 / @version 1.0

/ @since 1.0 //

public class DESCoderTest {

@Test
public void test() throws Exception {

    String inputStr = "DES";
    String key = DESCoder.initKey();

    System.err.println("原文:\t" + inputStr);


    System.err.println("密钥:\t" + key);


    byte[] inputData = inputStr.getBytes();
    inputData = DESCoder.encrypt(inputData, key);


    System.err.println("加密后:\t" + DESCoder.encryptBASE64(inputData));


    byte[] outputData = DESCoder.decrypt(inputData, key);

    String outputStr = new String(outputData);


    System.err.println("解密后:\t" + outputStr);


    assertEquals(inputStr, outputStr);
}

} 得到的输出内容如下: Console代码 复制代码 收藏代码

  1. 原文: DES
  2. 密钥: f3wEtRrV6q0=
  3. 加密后: C6qe9oNIzRY=
  4. 解密后: DES

原文: DES

密钥: f3wEtRrV6q0=

加密后: C6qe9oNIzRY=

解密后: DES 由控制台得到的输出,我们能够比对加密、解密后结果一致。这是一种简单的加密解密方式,只有一个密钥。 其实DES有很多同胞兄弟,如DESede(TripleDES)、AES、Blowfish、RC2、RC4(ARCFOUR)。这里就不过多阐述了,大同小异,只要换掉ALGORITHM换成对应的值,同时做一个代码替换SecretKey secretKey = new SecretKeySpec(key, ALGORITHM);就可以了,此外就是密钥长度不同了。 Java代码 复制代码 收藏代码

  1. ///
  2. /* DES key size must be equal to 56
  3. /* DESede(TripleDES) key size must be equal to 112 or 168
  4. /* AES key size must be equal to 128, 192 or 256,but 192 and 256 bits may not be available
  5. /* Blowfish key size must be multiple of 8, and can only range from 32 to 448 (inclusive)
  6. /* RC2 key size must be between 40 and 1024 bits
  7. /* RC4(ARCFOUR) key size must be between 40 and 1024 bits
  8. ///

///

/ DES key size must be equal to 56 / DESede(TripleDES) key size must be equal to 112 or 168

/ AES key size must be equal to 128, 192 or 256,but 192 and 256 bits may not be available / Blowfish key size must be multiple of 8, and can only range from 32 to 448 (inclusive)

/ RC2 key size must be between 40 and 1024 bits / RC4(ARCFOUR) key size must be between 40 and 1024 bits

/// 相关链接: Java加密技术(一)——BASE64与单向加密算法MD5&SHA&MAC Java加密技术(二)——对称加密算法DES&AES Java加密技术(三)——PBE算法 Java加密技术(四)——非对称加密算法RSA Java加密技术(五)——非对称加密算法的由来 Java加密技术(六)——数字签名算法DSA Java加密技术(七)——非对称加密算法最高ECC Java加密技术(八)——数字证书 Java加密技术(九)——初探SSL Java加密技术(十)——单向认证 Java加密技术(十一)——双向认证 Java加密技术(十二)——/.PFX(/.p12)&个人信息交换文件

Java加密技术(一)——BASE64与单向加密算法MD5&SHA&MAC

Posted on

Java加密技术(一)——BASE64与单向加密算法MD5&SHA&MAC

加密解密,曾经是我一个毕业设计的重要组件。在工作了多年以后回想当时那个加密、解密算法,实在是太单纯了。![]()
言归正传,这里我们主要描述Java已经实现的一些加密解密算法,最后介绍数字证书。
如基本的单向加密算法:
  • BASE64 严格地说,属于编码格式,而非加密算法
  • MD5(Message Digest algorithm 5,信息摘要算法)
  • SHA(Secure Hash Algorithm,安全散列算法)
  • HMAC(Hash Message Authentication Code,散列消息鉴别码) 复杂的对称加密(DES、PBE)、非对称加密算法:

  • DES(Data Encryption Standard,数据加密算法)

  • PBE(Password-based encryption,基于密码验证)
  • RSA(算法的名字以发明者的名字命名:Ron Rivest, AdiShamir 和Leonard Adleman)
  • DH(Diffie-Hellman算法,密钥一致协议)
  • DSA(Digital Signature Algorithm,数字签名)
  • ECC(Elliptic Curves Cryptography,椭圆曲线密码编码学) 本篇内容简要介绍BASE64MD5SHAHMAC几种方法。 MD5SHAHMAC这三种加密算法,可谓是非可逆加密,就是不可解密的加密方法。我们通常只把他们作为加密的基础。单纯的以上三种的加密并不可靠。 BASE64 按照RFC2045的定义,Base64被定义为:Base64内容传送编码被设计用来把任意序列的8位字节描述为一种不易被人直接识别的形式。(The Base64 Content-Transfer-Encoding is designed to represent arbitrary sequences of octets in a form that need not be humanly readable.) 常见于邮件、http加密,截取http信息,你就会发现登录操作的用户名、密码字段通过BASE64加密的。 通过java代码实现如下: Java代码 复制代码 收藏代码
  1. ///
  2. /* BASE64解密
  3. /*
  4. /* @param key
  5. /* @return
  6. /* @throws Exception
  7. /*/
  8. public static byte[] decryptBASE64(String key) throws Exception {
  9. return (new BASE64Decoder()).decodeBuffer(key);
  10. }
  11. ///
  12. /* BASE64加密
  13. /*
  14. /* @param key
  15. /* @return
  16. /* @throws Exception
  17. /*/
  18. public static String encryptBASE64(byte[] key) throws Exception {
  19. return (new BASE64Encoder()).encodeBuffer(key);
  20. }

    ///

    / BASE64解密 /

    / @param key / @return

    / @throws Exception //

    public static byte[] decryptBASE64(String key) throws Exception {

     return (new BASE64Decoder()).decodeBuffer(key);
    

    }

//*/*
 /* BASE64加密

 /*
 /* @param key

 /* @return
 /* @throws Exception

 /*/
public static String encryptBASE64(byte[] key) throws Exception {

    return (new BASE64Encoder()).encodeBuffer(key);
}

主要就是BASE64Encoder、BASE64Decoder两个类,我们只需要知道使用对应的方法即可。另,BASE加密后产生的字节位数是8的倍数,如果不够位数以=符号填充。 MD5 MD5 -- message-digest algorithm 5 (信息-摘要算法)缩写,广泛用于加密和解密技术,常用于文件校验。校验?不管文件多大,经过MD5后都能生成唯一的MD5值。好比现在的ISO校验,都是MD5校验。怎么用?当然是把ISO经过MD5后产生MD5的值。一般下载linux-ISO的朋友都见过下载链接旁边放着MD5的串。就是用来验证文件是否一致的。 通过java代码实现如下: Java代码 复制代码 收藏代码

  1. ///
  2. /* MD5加密
  3. /*
  4. /* @param data
  5. /* @return
  6. /* @throws Exception
  7. /*/
  8. public static byte[] encryptMD5(byte[] data) throws Exception {
  9. MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
  10. md5.update(data);
  11. return md5.digest();
  12. }

    ///

    / MD5加密 /

    / @param data / @return

    / @throws Exception //

    public static byte[] encryptMD5(byte[] data) throws Exception {

    MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
    md5.update(data);


    return md5.digest();


}

通常我们不直接使用上述MD5加密。通常将MD5产生的字节数组交给BASE64再加密一把,得到相应的字符串。 SHA SHA(Secure Hash Algorithm,安全散列算法),数字签名等密码学应用中重要的工具,被广泛地应用于电子商务等信息安全领域。虽然,SHA与MD5通过碰撞法都被破解了, 但是SHA仍然是公认的安全加密算法,较之MD5更为安全。 通过java代码实现如下: Java代码 复制代码 收藏代码

  1. ///
  2. /* SHA加密
  3. /*
  4. /* @param data
  5. /* @return
  6. /* @throws Exception
  7. /*/
  8. public static byte[] encryptSHA(byte[] data) throws Exception {
  9. MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
  10. sha.update(data);
  11. return sha.digest();
  12. }
  13. }

    ///

    / SHA加密 /

    / @param data / @return

    / @throws Exception //

    public static byte[] encryptSHA(byte[] data) throws Exception {

    MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
    sha.update(data);


    return sha.digest();


}

} HMAC HMAC(Hash Message Authentication Code,散列消息鉴别码,基于密钥的Hash算法的认证协议。消息鉴别码实现鉴别的原理是,用公开函数和密钥产生一个固定长度的值作为认证标识,用这个标识鉴别消息的完整性。使用一个密钥生成一个固定大小的小数据块,即MAC,并将其加入到消息中,然后传输。接收方利用与发送方共享的密钥进行鉴别认证等。 通过java代码实现如下: Java代码 复制代码 收藏代码

  1. ///
  2. /* 初始化HMAC密钥
  3. /*
  4. /* @return
  5. /* @throws Exception
  6. /*/
  7. public static String initMacKey() throws Exception {
  8. KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);
  9. SecretKey secretKey = keyGenerator.generateKey();
  10. return encryptBASE64(secretKey.getEncoded());
  11. }
  12. ///
  13. /* HMAC加密
  14. /*
  15. /* @param data
  16. /* @param key
  17. /* @return
  18. /* @throws Exception
  19. /*/
  20. public static byte[] encryptHMAC(byte[] data, String key) throws Exception {
  21. SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC);
  22. Mac mac = Mac.getInstance(secretKey.getAlgorithm());
  23. mac.init(secretKey);
  24. return mac.doFinal(data);
  25. }

    ///

    / 初始化HMAC密钥 /

    / @return / @throws Exception

    /*/ public static String initMacKey() throws Exception {

     KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);
    
    SecretKey secretKey = keyGenerator.generateKey();
    return encryptBASE64(secretKey.getEncoded());

}


//*/*
 /* HMAC加密

 /*
 /* @param data

 /* @param key
 /* @return

 /* @throws Exception
 /*/

public static byte[] encryptHMAC(byte[] data, String key) throws Exception {


    SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC);
    Mac mac = Mac.getInstance(secretKey.getAlgorithm());

    mac.init(secretKey);


    return mac.doFinal(data);


}

给出一个完整类,如下: Java代码 复制代码 收藏代码

  1. import java.security.MessageDigest;
  2. import javax.crypto.KeyGenerator;
  3. import javax.crypto.Mac;
  4. import javax.crypto.SecretKey;
  5. import sun.misc.BASE64Decoder;
  6. import sun.misc.BASE64Encoder;
  7. ///
  8. /* 基础加密组件
  9. /*
  10. /* @author 梁栋
  11. /* @version 1.0
  12. /* @since 1.0
  13. /*/
  14. public abstract class Coder {
  15. public static final String KEY_SHA = "SHA";
  16. public static final String KEY_MD5 = "MD5";
  17. ///
  18. /* MAC算法可选以下多种算法
  19. /*
  20. /*
     
  21. /* HmacMD5
  22. /* HmacSHA1
  23. /* HmacSHA256
  24. /* HmacSHA384
  25. /* HmacSHA512
  26. /*
  27. /*/
  28. public static final String KEY_MAC = "HmacMD5";
  29. ///
  30. /* BASE64解密
  31. /*
  32. /* @param key
  33. /* @return
  34. /* @throws Exception
  35. /*/
  36. public static byte[] decryptBASE64(String key) throws Exception {
  37. return (new BASE64Decoder()).decodeBuffer(key);
  38. }
  39. ///
  40. /* BASE64加密
  41. /*
  42. /* @param key
  43. /* @return
  44. /* @throws Exception
  45. /*/
  46. public static String encryptBASE64(byte[] key) throws Exception {
  47. return (new BASE64Encoder()).encodeBuffer(key);
  48. }
  49. ///
  50. /* MD5加密
  51. /*
  52. /* @param data
  53. /* @return
  54. /* @throws Exception
  55. /*/
  56. public static byte[] encryptMD5(byte[] data) throws Exception {
  57. MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);
  58. md5.update(data);
  59. return md5.digest();
  60. }
  61. ///
  62. /* SHA加密
  63. /*
  64. /* @param data
  65. /* @return
  66. /* @throws Exception
  67. /*/
  68. public static byte[] encryptSHA(byte[] data) throws Exception {
  69. MessageDigest sha = MessageDigest.getInstance(KEY_SHA);
  70. sha.update(data);
  71. return sha.digest();
  72. }
  73. ///
  74. /* 初始化HMAC密钥
  75. /*
  76. /* @return
  77. /* @throws Exception
  78. /*/
  79. public static String initMacKey() throws Exception {
  80. KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);
  81. SecretKey secretKey = keyGenerator.generateKey();
  82. return encryptBASE64(secretKey.getEncoded());
  83. }
  84. ///
  85. /* HMAC加密
  86. /*
  87. /* @param data
  88. /* @param key
  89. /* @return
  90. /* @throws Exception
  91. /*/
  92. public static byte[] encryptHMAC(byte[] data, String key) throws Exception {
  93. SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC);
  94. Mac mac = Mac.getInstance(secretKey.getAlgorithm());
  95. mac.init(secretKey);
  96. return mac.doFinal(data);
  97. }
  98. }

import java.security.MessageDigest;

import javax.crypto.KeyGenerator;

import javax.crypto.Mac; import javax.crypto.SecretKey;

import sun.misc.BASE64Decoder;

import sun.misc.BASE64Encoder;

/// /* 基础加密组件

/ / @author 梁栋

/ @version 1.0 / @since 1.0

/*/ public abstract class Coder {

public static final String KEY_SHA = "SHA";
public static final String KEY_MD5 = "MD5";


//*/*

 /* MAC算法可选以下多种算法
 /*

 /* <pre>
 /* HmacMD5

 /* HmacSHA1
 /* HmacSHA256

 /* HmacSHA384
 /* HmacSHA512

 /* </pre>
 /*/

public static final String KEY_MAC = "HmacMD5";


//*/*
 /* BASE64解密

 /*
 /* @param key

 /* @return
 /* @throws Exception

 /*/
public static byte[] decryptBASE64(String key) throws Exception {

    return (new BASE64Decoder()).decodeBuffer(key);
}


//*/*

 /* BASE64加密
 /*

 /* @param key
 /* @return

 /* @throws Exception
 /*/

public static String encryptBASE64(byte[] key) throws Exception {
    return (new BASE64Encoder()).encodeBuffer(key);

}


//*/*
 /* MD5加密

 /*
 /* @param data

 /* @return
 /* @throws Exception

 /*/
public static byte[] encryptMD5(byte[] data) throws Exception {


    MessageDigest md5 = MessageDigest.getInstance(KEY_MD5);

    md5.update(data);


    return md5.digest();


}


//*/*
 /* SHA加密

 /*
 /* @param data

 /* @return
 /* @throws Exception

 /*/
public static byte[] encryptSHA(byte[] data) throws Exception {


    MessageDigest sha = MessageDigest.getInstance(KEY_SHA);

    sha.update(data);


    return sha.digest();


}


//*/*
 /* 初始化HMAC密钥

 /*
 /* @return

 /* @throws Exception
 /*/

public static String initMacKey() throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_MAC);


    SecretKey secretKey = keyGenerator.generateKey();

    return encryptBASE64(secretKey.getEncoded());
}


//*/*

 /* HMAC加密
 /*

 /* @param data
 /* @param key

 /* @return
 /* @throws Exception

 /*/
public static byte[] encryptHMAC(byte[] data, String key) throws Exception {


    SecretKey secretKey = new SecretKeySpec(decryptBASE64(key), KEY_MAC);

    Mac mac = Mac.getInstance(secretKey.getAlgorithm());
    mac.init(secretKey);


    return mac.doFinal(data);


}

} 再给出一个测试类: Java代码 复制代码 收藏代码

  1. import static org.junit.Assert./*;
  2. import org.junit.Test;
  3. ///
  4. /*
  5. /* @author 梁栋
  6. /* @version 1.0
  7. /* @since 1.0
  8. /*/
  9. public class CoderTest {
  10. @Test
  11. public void test() throws Exception {
  12. String inputStr = "简单加密";
  13. System.err.println("原文:\n" + inputStr);
  14. byte[] inputData = inputStr.getBytes();
  15. String code = Coder.encryptBASE64(inputData);
  16. System.err.println("BASE64加密后:\n" + code);
  17. byte[] output = Coder.decryptBASE64(code);
  18. String outputStr = new String(output);
  19. System.err.println("BASE64解密后:\n" + outputStr);
  20. // 验证BASE64加密解密一致性
  21. assertEquals(inputStr, outputStr);
  22. // 验证MD5对于同一内容加密是否一致
  23. assertArrayEquals(Coder.encryptMD5(inputData), Coder
  24. .encryptMD5(inputData));
  25. // 验证SHA对于同一内容加密是否一致
  26. assertArrayEquals(Coder.encryptSHA(inputData), Coder
  27. .encryptSHA(inputData));
  28. String key = Coder.initMacKey();
  29. System.err.println("Mac密钥:\n" + key);
  30. // 验证HMAC对于同一内容,同一密钥加密是否一致
  31. assertArrayEquals(Coder.encryptHMAC(inputData, key), Coder.encryptHMAC(
  32. inputData, key));
  33. BigInteger md5 = new BigInteger(Coder.encryptMD5(inputData));
  34. System.err.println("MD5:\n" + md5.toString(16));
  35. BigInteger sha = new BigInteger(Coder.encryptSHA(inputData));
  36. System.err.println("SHA:\n" + sha.toString(32));
  37. BigInteger mac = new BigInteger(Coder.encryptHMAC(inputData, inputStr));
  38. System.err.println("HMAC:\n" + mac.toString(16));
  39. }
  40. }

import static org.junit.Assert./*;

import org.junit.Test;

///

/ / @author 梁栋

/ @version 1.0 / @since 1.0

/*/ public class CoderTest {

@Test

public void test() throws Exception {
    String inputStr = "简单加密";

    System.err.println("原文:\n" + inputStr);


    byte[] inputData = inputStr.getBytes();
    String code = Coder.encryptBASE64(inputData);


    System.err.println("BASE64加密后:\n" + code);


    byte[] output = Coder.decryptBASE64(code);


    String outputStr = new String(output);


    System.err.println("BASE64解密后:\n" + outputStr);


    // 验证BASE64加密解密一致性

    assertEquals(inputStr, outputStr);


    // 验证MD5对于同一内容加密是否一致
    assertArrayEquals(Coder.encryptMD5(inputData), Coder

            .encryptMD5(inputData));


    // 验证SHA对于同一内容加密是否一致
    assertArrayEquals(Coder.encryptSHA(inputData), Coder

            .encryptSHA(inputData));


    String key = Coder.initMacKey();
    System.err.println("Mac密钥:\n" + key);


    // 验证HMAC对于同一内容,同一密钥加密是否一致

    assertArrayEquals(Coder.encryptHMAC(inputData, key), Coder.encryptHMAC(
            inputData, key));


    BigInteger md5 = new BigInteger(Coder.encryptMD5(inputData));

    System.err.println("MD5:\n" + md5.toString(16));


    BigInteger sha = new BigInteger(Coder.encryptSHA(inputData));
    System.err.println("SHA:\n" + sha.toString(32));


    BigInteger mac = new BigInteger(Coder.encryptHMAC(inputData, inputStr));

    System.err.println("HMAC:\n" + mac.toString(16));
}

} 控制台输出: Console代码 复制代码 收藏代码

  1. 原文:
  2. 简单加密
  3. BASE64加密后:
  4. 566A5Y2V5Yqg5a+G
  5. BASE64解密后:
  6. 简单加密
  7. Mac密钥:
  8. uGxdHC+6ylRDaik++leFtGwiMbuYUJ6mqHWyhSgF4trVkVBBSQvY/a22xU8XT1RUemdCWW155Bke
  9. pBIpkd7QHg==
  10. MD5:
  11. -550b4d90349ad4629462113e7934de56
  12. SHA:
  13. 91k9vo7p400cjkgfhjh0ia9qthsjagfn
  14. HMAC:
  15. 2287d192387e95694bdbba2fa941009a

原文:

简单加密 BASE64加密后:

566A5Y2V5Yqg5a+G

BASE64解密后: 简单加密

Mac密钥: uGxdHC+6ylRDaik++leFtGwiMbuYUJ6mqHWyhSgF4trVkVBBSQvY/a22xU8XT1RUemdCWW155Bke

pBIpkd7QHg==

MD5: -550b4d90349ad4629462113e7934de56

SHA: 91k9vo7p400cjkgfhjh0ia9qthsjagfn

HMAC: 2287d192387e95694bdbba2fa941009a

注意 编译时,可能会看到如下提示: 引用

警告:sun.misc.BASE64Decoder 是 Sun 的专用 API,可能会在未来版本中删除 import sun.misc.BASE64Decoder; ^ 警告:sun.misc.BASE64Encoder 是 Sun 的专用 API,可能会在未来版本中删除 import sun.misc.BASE64Encoder; ^ BASE64Encoder和BASE64Decoder是非官方JDK实现类。虽然可以在JDK里能找到并使用,但是在API里查不到。JRE 中 sun 和 com.sun 开头包的类都是未被文档化的,他们属于 java, javax 类库的基础,其中的实现大多数与底层平台有关,一般来说是不推荐使用的。 BASE64的加密解密是双向的,可以求反解。 MD5、SHA以及HMAC是单向加密,任何数据加密后只会产生唯一的一个加密串,通常用来校验数据在传输过程中是否被修改。其中HMAC算法有一个密钥,增强了数据传输过程中的安全性,强化了算法外的不可控因素。 单向加密的用途主要是为了校验数据在传输过程中是否被修改。 相关链接: Java加密技术(一)——BASE64与单向加密算法MD5&SHA&MAC Java加密技术(二)——对称加密DES&AES Java加密技术(三)——PBE算法 Java加密技术(四)——非对称加密算法RSA Java加密技术(五)——非对称加密算法的由来DH Java加密技术(六)——数字签名算法DSA Java加密技术(七)——非对称加密算法最高ECC Java加密技术(八)——数字证书 Java加密技术(九)——初探SSL Java加密技术(十)——单向认证 Java加密技术(十一)——双向认证 Java加密技术(十二)——/.PFX(/.p12)&个人信息交换文件

跟我一起阅读Java源代码之HashMap

Posted on

跟我一起阅读Java源代码之HashMap

最近闲的很,想和大家一起学习并讨论下Java的一些源代码以及其实现的数据结构,

不是什么高水平的东西,有兴趣的随便看看

  1. 为什么要用Map,以HashMap为例

    很多时候我们有这样的需求,我们需要将数据成键值对的方式存储起来,根据key来获取value(value可以是简单值,也可以是自定义对象)

    当然用对象数组也能实现这个目的,查找时可以遍历数组,比较关键字来获取对应的value

    从性能上来讲,遍历大数组会消耗性能

    从API易用性来讲,需要自己实现查找的逻辑

    所以用HashMap是必要的

  1. HashMap的数据结构是怎么样的
我一直对HashMap的内部结构很好奇,看了源码之后发现他是用散列实现的,即基于hashcode

大体思想是这样的

1. 首先建立一个数组用来存取数据,假设我们定义一个Object[] table用来存取map的value

这个很容易理解,key存在哪里呢?暂时我不想存储key

2. 获得key的hashcode经过一定算法转成一个整数

    index,这个index的取值范围必须是0=<index<table.length,然后我将其作为数组元素的下标

    比如执行这样的操作:table[index] = value;

    这样存储的问题解决了

3. 如何通过key去获取这个value呢

    这个太简单了,首先获取key的hashcode,然后通过刚才一样的算法得出元素下标index

    然后value = table[index]

简单的HashTable实现如下

Java代码 收藏代码

  1. public class SimpleHashMap {
  2. private Object[] table;
  3. public SimpleHashMap() {
  4. table = new Object[10];
  5. }
  6. public Object get(Object key) {
  7. int index = indexFor(hash(key.hashCode()), 10);
  8. return table[index];
  9. }
  10. public void put(Object key, Object value) {
  11. int index = indexFor(hash(key.hashCode()), 10);
  12. table[index] = value;
  13. }
  14. ///
  15. /* 通过hash code 和table的length得到对应的数组下标
  16. /*
  17. /* @param h
  18. /* @param length
  19. /* @return
  20. /*/
  21. static int indexFor(int h, int length) {
  22. return h & (length - 1);
  23. }
  24. ///
  25. /* 通过一定算法计算出新的hash值
  26. /*
  27. /* @param h
  28. /* @return
  29. /*/
  30. static int hash(int h) {
  31. h ^= (h >>> 20) ^ (h >>> 12);
  32. return h ^ (h >>> 7) ^ (h >>> 4);
  33. }
  34. public static void main(String[] args){
  35. SimpleHashMap hashMap = new SimpleHashMap();
  36. hashMap.put("key", "value");
  37. System.out.println(hashMap.get("key"));
  38. }
  39. }

这个简单的例子大概描述了散列实现hashmap的过程

但是还很不成熟,我发现至少存在以下两个问题

  1. hashmap的size是固定的

  2. 如果不同的key通过hashcode得出的index相同呢,这样的情况是存在的,如何解决? 来源: [http://tangyanbo.iteye.com/blog/1755636](http://tangyanbo.iteye.com/blog/1755636) 由于table的大小是有限的,而key的集合范围是无限大的,所以寄希望于hashcode散落,肯定会出现多个key散落在同一个数组下标下面,

因此我们要引入另外一个概念,将key和value同时存入table[index]中,即将key和value构成一个对象放在table[index],而且可能存放多个,他们的key对应的index相同,但是key本身不同

现在我们就该讨论以什么样的方式存储这些散落在同一个数组下标的元素

可以考虑数组?

也可以考虑链表存储

源码里面是用链表存储的,其实我也没明白这两种方式在这里有什么区别

,感觉无论在检索和存储上都是差不多的效率,

检索都是需要遍历的方式,而存储也可以是顺序的

那这个问题留给大家吧。

我们来实现链式存储的方式,首先定义一个链表数据结构Entry:

Java代码 收藏代码

  1. public class Entry {
  2. //存储key
  3. final K key;
  4. //存储value
  5. V value;
  6. //存储指向下一个节点的指针
  7. Entry next;
  8. //存储key映射的hash
  9. final int hash;
  10. }

新的EntryHashMap实现方式

Java代码 收藏代码

  1. public class EntryHashMap {
  2. transient Entry[] table;
  3. transient int size;
  4. public V put(K key, V value) {
  5. //计算出新的hash
  6. int hash = hash(key.hashCode());
  7. //计算出数组小标i
  8. int i = indexFor(hash, table.length);
  9. //遍历table[i],如果table[i]没有与新加入的key相等的,则新加入
  10. //一个value到table[i]中的entry,否则将新的value覆盖旧的value并返回旧的value
  11. for (Entry e = table[i]; e != null; e = e.next) {
  12. Object k;
  13. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  14. V oldValue = e.value;
  15. e.value = value;
  16. return oldValue;
  17. }
  18. }
  19. addEntry(hash, key, value, i);
  20. return null;
  21. }
  22. public void addEntry(int hash, K key, V value, int bucketIndex) {
  23. Entry e = table[bucketIndex];
  24. //将新的元素插入链表前端
  25. table[bucketIndex] = new Entry<>(hash, key, value, e);
  26. size++;
  27. }
  28. ///
  29. /* 通过hash code 和table的length得到对应的数组下标
  30. /*
  31. /* @param h
  32. /* @param length
  33. /* @return
  34. /*/
  35. static int indexFor(int h, int length) {
  36. return h & (length - 1);
  37. }
  38. ///
  39. /* 通过一定算法计算出新的hash值
  40. /*
  41. /* @param h
  42. /* @return
  43. /*/
  44. static int hash(int h) {
  45. h ^= (h >>> 20) ^ (h >>> 12);
  46. return h ^ (h >>> 7) ^ (h >>> 4);
  47. }
  48. }

来源: [http://tangyanbo.iteye.com/blog/1756074](http://tangyanbo.iteye.com/blog/1756074) 为什么要用链表而不是数组

链表的作用有如下两点好处

  1. remove操作时效率高,只维护指针的变化即可,无需进行移位操作

  2. 重新散列时,原来散落在同一个槽中的元素可能会被散落在不同的地方,对于数组需要进行移位操作,而链表只需维护指针

今天研究下数组长度不够时的处理办法

table为散列数组

  1. 首先定义一个不可修改的静态变量存储table的初始大小 DEFAULT_INITIAL_CAPACITY

  2. 定义一个全局变量存储table的实际元素长度,size

  3. 定义一个全局变量存储临界点,即元素的size>=threshold这个临界点时,扩大table的容量

  4. 因为index是根据hash和table的长度计算得到的,所以还需要重新对所有元素进行散列

实现如下:

Java代码 收藏代码

  1. package sourcecoderead.collection.map;
  2. public class EntryHashMap {
  3. /// 初始容量 /*/
  4. static final int DEFAULT_INITIAL_CAPACITY = 16;
  5. static final float DEFAULT_LOAD_FACTOR = 0.75f;
  6. /// 下次扩容的临界值 /*/
  7. int threshold;
  8. transient int size;
  9. final float loadFactor;
  10. transient Entry[] table;
  11. public EntryHashMap() {
  12. this.loadFactor = DEFAULT_LOAD_FACTOR;
  13. threshold = (int) (DEFAULT_INITIAL_CAPACITY /* DEFAULT_LOAD_FACTOR);
  14. table = new Entry[DEFAULT_INITIAL_CAPACITY];
  15. }
  16. public V put(K key, V value) {
  17. // 计算出新的hash
  18. int hash = hash(key.hashCode());
  19. // 计算出数组小标i
  20. int i = indexFor(hash, table.length);
  21. // 遍历table[i],如果table[i]没有与新加入的key相等的,则新加入
  22. // 一个value到table[i]中的entry,否则将新的value覆盖旧的value并返回旧的value
  23. for (Entry e = table[i]; e != null; e = e.next) {
  24. Object k;
  25. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  26. V oldValue = e.value;
  27. e.value = value;
  28. return oldValue;
  29. }
  30. }
  31. addEntry(hash, key, value, i);
  32. return null;
  33. }
  34. public V get(K key) {
  35. // 计算出新的hash
  36. int hash = hash(key.hashCode());
  37. // 计算出数组小标i
  38. int i = indexFor(hash, table.length);
  39. for (Entry e = table[i]; e != null; e = e.next) {
  40. Object k;
  41. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  42. return e.value;
  43. }
  44. }
  45. return null;
  46. }
  47. private void addEntry(int hash, K key, V value, int bucketIndex) {
  48. Entry e = table[bucketIndex];
  49. // 将新的元素插入链表前端
  50. table[bucketIndex] = new Entry<>(hash, key, value, e);
  51. if (size++ >= threshold)
  52. resize(2 /* table.length);
  53. }
  54. void resize(int newCapacity) {
  55. Entry[] oldTable = table;
  56. int oldCapacity = oldTable.length;
  57. Entry[] newTable = new Entry[newCapacity];
  58. transfer(newTable);
  59. table = newTable;
  60. threshold = (int) (newCapacity /* loadFactor);
  61. }
  62. void transfer(Entry[] newTable) {
  63. Entry[] src = table;
  64. int newCapacity = newTable.length;
  65. for (int j = 0; j < src.length; j++) {
  66. Entry e = src[j];
  67. if (e != null) {
  68. src[j] = null;
  69. do {
  70. Entry next = e.next;
  71. int i = indexFor(e.hash, newCapacity);
  72. e.next = newTable[i];
  73. newTable[i] = e;
  74. e = next;
  75. } while (e != null);
  76. }
  77. }
  78. }
  79. ///
  80. /* 通过hash code 和table的length得到对应的数组下标
  81. /*
  82. /* @param h
  83. /* @param length
  84. /* @return
  85. /*/
  86. static int indexFor(int h, int length) {
  87. return h & (length - 1);
  88. }
  89. ///
  90. /* 通过一定算法计算出新的hash值
  91. /*
  92. /* @param h
  93. /* @return
  94. /*/
  95. static int hash(int h) {
  96. h ^= (h >>> 20) ^ (h >>> 12);
  97. return h ^ (h >>> 7) ^ (h >>> 4);
  98. }
  99. public static void main(String[] args) {
  100. EntryHashMap hashMap = new EntryHashMap();
  101. hashMap.put("key", "value");
  102. System.out.println(hashMap.get("key"));
  103. }
  104. }

来源: [http://tangyanbo.iteye.com/blog/1756536](http://tangyanbo.iteye.com/blog/1756536)

通过分析 JDK 源代码研究 Hash 存储机制

Posted on

通过分析 JDK 源代码研究 Hash 存储机制

IBM® 跳转到主要内容 中国 [选择] 使用条款 Select a scope: dW 全部内容 ----------------- AIX and UNIX Information management Lotus Rational WebSphere ----------------- Architecture Grid computing Java 技术 Linux Multicore acceleration Open source Security SOA & Web services Web development XML ----------------- IBM 全部内容 Search for: 首页 产品 服务与解决方案 支持与下载 个性化服务 developerWorks 中国 本文内容包括: 通过 HashMap、HashSet 的源代码分析其 Hash 存储机制 HashMap 的存储实现 Hash 算法的性能选项 HashMap 的读取实现 HashSet 的实现 参考资料 关于作者 对本文的评价 相关链接: Java technology 技术文档库 跳转到主要内容 developerWorks 中国 > Java technology >

通过分析 JDK 源代码研究 Hash 存储机制

developerWorks 文档选项 未显示需要 JavaScript 的文档选项

级别: 中级

李 刚, 自由撰稿人

2009 年 11 月 26 日 HashMap 和 HashSet 是 Java Collection Framework 的两个重要成员,其中 HashMap 是 Map 接口的常用实现类,HashSet 是 Set 接口的常用实现类。虽然 HashMap 和 HashSet 实现的接口规范不同,但它们底层的 Hash 存储机制完全一样,甚至 HashSet 本身就采用 HashMap 来实现的。

通过 HashMap、HashSet 的源代码分析其 Hash 存储机制

集合和引用

就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并不是真正的把 Java 对象放入数组中,只是把对象的引用放入数组中,每个数组元素都是一个引用变量。

实际上,HashSet 和 HashMap 之间有很多相似之处,对于 HashSet 而言,系统采用 Hash 算法决定集合元素的存储位置,这样可以保证能快速存、取集合元素;对于 HashMap 而言,系统 key-value 当成一个整体进行处理,系统总是根据 Hash 算法来计算 key-value 的存储位置,这样可以保证能快速存、取 Map 的 key-value 对。

在介绍集合存储之前需要指出一点:虽然集合号称存储的是 Java 对象,但实际上并不会真正将 Java 对象放入 Set 集合中,只是在 Set 集合中保留这些对象的引用而言。也就是说:Java 集合实际上是多个引用变量所组成的集合,这些引用变量指向实际的 Java 对象。 回页首

HashMap 的存储实现

当程序试图将多个 key-value 放入 HashMap 中时,以如下代码片段为例: HashMap map = new HashMap(); map.put("语文" , 80.0); map.put("数学" , 89.0); map.put("英语" , 78.2);

HashMap 采用一种所谓的“Hash 算法”来决定每个元素的存储位置。

当程序执行 map.put("语文" , 80.0); 时,系统将调用"语文"的 hashCode() 方法得到其 hashCode 值——每个 Java 对象都有 hashCode() 方法,都可通过该方法获得它的 hashCode 值。得到这个对象的 hashCode 值之后,系统会根据该 hashCode 值来决定该元素的存储位置。

我们可以看 HashMap 类的 put(K key , V value) 方法的源代码: public V put(K key, V value) { // 如果 key 为 null,调用 putForNullKey 方法进行处理 if (key == null) return putForNullKey(value); // 根据 key 的 keyCode 计算 Hash 值 int hash = hash(key.hashCode()); // 搜索指定 hash 值在对应 table 中的索引 int i = indexFor(hash, table.length); // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素 for (Entry e = table[i]; e != null; e = e.next) { Object k; // 找到指定 key 与需要放入的 key 相等(hash 值相同 // 通过 equals 比较放回 true) if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 如果 i 索引处的 Entry 为 null,表明此处还没有 Entry modCount++; // 将 key、value 添加到 i 索引处 addEntry(hash, key, value, i); return null; } JDK 源码

在 JDK 安装目录下可以找到一个 src.zip 压缩文件,该文件里包含了 Java 基础类库的所有源文件。只要读者有学习兴趣,随时可以打开这份压缩文件来阅读 Java 类库的源代码,这对提高读者的编程能力是非常有帮助的。需要指出的是:src.zip 中包含的源代码并没有包含像上文中的中文注释,这些注释是笔者自己添加进去的。

上面程序中用到了一个重要的内部接口:Map.Entry,每个 Map.Entry 其实就是一个 key-value 对。从上面程序中可以看出:当系统决定存储 HashMap 中的 key-value 对时,完全没有考虑 Entry 中的 value,仅仅只是根据 key 来计算并决定每个 Entry 的存储位置。这也说明了前面的结论:我们完全可以把 Map 集合中的 value 当成 key 的附属,当系统决定了 key 的存储位置之后,value 随之保存在那里即可。

上面方法提供了一个根据 hashCode() 返回值来计算 Hash 码的方法:hash(),这个方法是一个纯粹的数学计算,其方法如下: static int hash(int h) { h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); }

对于任意给定的对象,只要它的 hashCode() 返回值相同,那么程序调用 hash(int h) 方法所计算得到的 Hash 码值总是相同的。接下来程序会调用 indexFor(int h, int length) 方法来计算该对象应该保存在 table 数组的哪个索引处。indexFor(int h, int length) 方法的代码如下: static int indexFor(int h, int length) { return h & (length-1); }

这个方法非常巧妙,它总是通过 h

& (table.length -1) 来得到该对象的保存位置——而 HashMap 底层数组的长度总是 2 的 n 次方,这一点可参看后面关于 HashMap 构造器的介绍。

当 length 总是 2 的倍数时,h

& (length-1)

将是一个非常巧妙的设计:假设 h=5,length=16, 那么 h & length - 1 将得到 5;如果 h=6,length=16, 那么 h & length - 1 将得到 6 ……如果 h=15,length=16, 那么 h & length - 1 将得到 15;但是当 h=16 时 , length=16 时,那么 h & length - 1 将得到 0 了;当 h=17 时 , length=16 时,那么 h & length - 1 将得到 1 了……这样保证计算得到的索引值总是位于 table 数组的索引之内。

根据上面 put 方法的源代码可以看出,当程序试图将一个 key-value 对放入 HashMap 中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:如果两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。如果这两个 Entry 的 key 通过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但 key 不会覆盖。如果这两个 Entry 的 key 通过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 形成 Entry 链,而且新添加的 Entry 位于 Entry 链的头部——具体说明继续看 addEntry() 方法的说明。

当向 HashMap 中添加 key-value 对,由其 key 的 hashCode() 返回值决定该 key-value 对(就是 Entry 对象)的存储位置。当两个 Entry 对象的 key 的 hashCode() 返回值相同时,将由 key 通过 eqauls() 比较值决定是采用覆盖行为(返回 true),还是产生 Entry 链(返回 false)。

上面程序中还调用了 addEntry(hash, key, value, i); 代码,其中 addEntry 是 HashMap 提供的一个包访问权限的方法,该方法仅用于添加一个 key-value 对。下面是该方法的代码: void addEntry(int hash, K key, V value, int bucketIndex) { // 获取指定 bucketIndex 索引处的 Entry Entry e = table[bucketIndex]; // ① // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry table[bucketIndex] = new Entry(hash, key, value, e); // 如果 Map 中的 key-value 对的数量超过了极限 if (size++ >= threshold) // 把 table 对象的长度扩充到 2 倍。 resize(2 /* table.length); // ② }

上面方法的代码很简单,但其中包含了一个非常优雅的设计:系统总是将新添加的 Entry 对象放入 table 数组的 bucketIndex 索引处——如果 bucketIndex 索引处已经有了一个 Entry 对象,那新添加的 Entry 对象指向原有的 Entry 对象(产生一个 Entry 链),如果 bucketIndex 索引处没有 Entry 对象,也就是上面程序①号代码的 e 变量是 null,也就是新放入的 Entry 对象指向 null,也就是没有产生 Entry 链。 回页首

Hash 算法的性能选项

根据上面代码可以看出,在同一个 bucket 存储 Entry 链的情况下,新放入的 Entry 总是位于 bucket 中,而最早放入该 bucket 中的 Entry 则位于这个 Entry 链的最末端。

上面程序中还有这样两个变量:

  • size:该变量保存了该 HashMap 中所包含的 key-value 对的数量。
  • threshold:该变量包含了 HashMap 能容纳的 key-value 对的极限,它的值等于 HashMap 的容量乘以负载因子(load factor)。

从上面程序中②号代码可以看出,当 size++ >= threshold 时,HashMap 会自动调用 resize 方法扩充 HashMap 的容量。每扩充一次,HashMap 的容量就增大一倍。

上面程序中使用的 table 其实就是一个普通数组,每个数组都有一个固定的长度,这个数组的长度就是 HashMap 的容量。HashMap 包含如下几个构造器:

  • HashMap():构建一个初始容量为 16,负载因子为 0.75 的 HashMap。
  • HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的 HashMap。
  • HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个 HashMap。

当创建一个 HashMap 时,系统会自动创建一个 table 数组来保存 HashMap 中的 Entry,下面是 HashMap 中一个构造器的代码: // 以指定初始化容量、负载因子创建 HashMap public HashMap(int initialCapacity, float loadFactor) { // 初始容量不能为负数 if (initialCapacity < 0) throw new IllegalArgumentException( "Illegal initial capacity: " + initialCapacity); // 如果初始容量大于最大容量,让出示容量 if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; // 负载因子必须大于 0 的数值 if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException( loadFactor); // 计算出大于 initialCapacity 的最小的 2 的 n 次方值。 int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; this.loadFactor = loadFactor; // 设置容量极限等于容量 / 负载因子 threshold = (int)(capacity / loadFactor); // 初始化 table 数组 table = new Entry[capacity]; // ① init(); }

上面代码中粗体字代码包含了一个简洁的代码实现:找出大于 initialCapacity 的、最小的 2 的 n 次方值,并将其作为 HashMap 的实际容量(由 capacity 变量保存)。例如给定 initialCapacity 为 10,那么该 HashMap 的实际容量就是 16。 initialCapacity 与 HashTable 的容量

创建 HashMap 时指定的 initialCapacity 并不等于 HashMap 的实际容量,通常来说,HashMap 的实际容量总比 initialCapacity 大一些,除非我们指定的 initialCapacity 参数值恰好是 2 的 n 次方。当然,掌握了 HashMap 容量分配的知识之后,应该在创建 HashMap 时将 initialCapacity 参数值指定为 2 的 n 次方,这样可以减少系统的计算开销。

程序①号代码处可以看到:table 的实质就是一个数组,一个长度为 capacity 的数组。

对于 HashMap 及其子类而言,它们采用 Hash 算法来决定集合中元素的存储位置。当系统开始初始化 HashMap 时,系统会创建一个长度为 capacity 的 Entry 数组,这个数组里可以存储元素的位置被称为“桶(bucket)”,每个 bucket 都有其指定索引,系统可以根据其索引快速访问该 bucket 里存储的元素。

无论何时,HashMap 的每个“桶”只存储一个元素(也就是一个 Entry),由于 Entry 对象可以包含一个引用变量(就是 Entry 构造器的的最后一个参数)用于指向下一个 Entry,因此可能出现的情况是:HashMap 的 bucket 中只有一个 Entry,但这个 Entry 指向另一个 Entry ——这就形成了一个 Entry 链。如图 1 所示: 图 1. HashMap 的存储示意 图 1. HashMap 的存储示意 回页首

HashMap 的读取实现

当 HashMap 的每个 bucket 里存储的 Entry 只是单个 Entry ——也就是没有通过指针产生 Entry 链时,此时的 HashMap 具有最好的性能:当程序通过 key 取出对应 value 时,系统只要先计算出该 key 的 hashCode() 返回值,在根据该 hashCode 返回值找出该 key 在 table 数组中的索引,然后取出该索引处的 Entry,最后返回该 key 对应的 value 即可。看 HashMap 类的 get(K key) 方法代码: public V get(Object key) { // 如果 key 是 null,调用 getForNullKey 取出对应的 value if (key == null) return getForNullKey(); // 根据该 key 的 hashCode 值计算它的 hash 码 int hash = hash(key.hashCode()); // 直接取出 table 数组中指定索引处的值, for (Entry e = table[indexFor(hash, table.length)]; e != null; // 搜索该 Entry 链的下一个 Entr e = e.next) // ① { Object k; // 如果该 Entry 的 key 与被搜索 key 相同 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) return e.value; } return null; }

从上面代码中可以看出,如果 HashMap 的每个 bucket 里只有一个 Entry 时,HashMap 可以根据索引、快速地取出该 bucket 里的 Entry;在发生“Hash 冲突”的情况下,单个 bucket 里存储的不是一个 Entry,而是一个 Entry 链,系统只能必须按顺序遍历每个 Entry,直到找到想搜索的 Entry 为止——如果恰好要搜索的 Entry 位于该 Entry 链的最末端(该 Entry 是最早放入该 bucket 中),那系统必须循环到最后才能找到该元素。

归纳起来简单地说,HashMap 在底层将 key-value 当成一个整体进行处理,这个整体就是一个 Entry 对象。HashMap 底层采用一个 Entry[] 数组来保存所有的 key-value 对,当需要存储一个 Entry 对象时,会根据 Hash 算法来决定其存储位置;当需要取出一个 Entry 时,也会根据 Hash 算法找到其存储位置,直接取出该 Entry。由此可见:HashMap 之所以能快速存、取它所包含的 Entry,完全类似于现实生活中母亲从小教我们的:不同的东西要放在不同的位置,需要时才能快速找到它。

当创建 HashMap 时,有一个默认的负载因子(load factor),其默认值为 0.75,这是时间和空间成本上一种折衷:增大负载因子可以减少 Hash 表(就是那个 Entry 数组)所占用的内存空间,但会增加查询数据的时间开销,而查询是最频繁的的操作(HashMap 的 get() 与 put() 方法都要用到查询);减小负载因子会提高数据查询的性能,但会增加 Hash 表所占用的内存空间。

掌握了上面知识之后,我们可以在创建 HashMap 时根据实际需要适当地调整 load factor 的值;如果程序比较关心空间开销、内存比较紧张,可以适当地增加负载因子;如果程序比较关心时间开销,内存比较宽裕则可以适当的减少负载因子。通常情况下,程序员无需改变负载因子的值。

如果开始就知道 HashMap 会保存多个 key-value 对,可以在创建时就使用较大的初始化容量,如果 HashMap 中 Entry 的数量一直不会超过极限容量(capacity / load factor),HashMap 就无需调用 resize() 方法重新分配 table 数组,从而保证较好的性能。当然,开始就将初始容量设置太高可能会浪费空间(系统需要创建一个长度为 capacity 的 Entry 数组),因此创建 HashMap 时初始化容量设置也需要小心对待。 [*回页首](http://www.ibm.com/developerworks/cn/java/j-lo-hash/?open&cm_mmc=6505-_-n-_-vrm_newsletter-_-10104_142587&cmibm_em=dm:0:10631101#main)

HashSet 的实现

对于 HashSet 而言,它是基于 HashMap 实现的,HashSet 底层采用 HashMap 来保存所有元素,因此 HashSet 的实现比较简单,查看 HashSet 的源代码,可以看到如下代码: public class HashSet extends AbstractSet implements Set, Cloneable, java.io.Serializable { // 使用 HashMap 的 key 保存 HashSet 中所有元素 private transient HashMap map; // 定义一个虚拟的 Object 对象作为 HashMap 的 value private static final Object PRESENT = new Object(); ... // 初始化 HashSet,底层会初始化一个 HashMap public HashSet() { map = new HashMap(); } // 以指定的 initialCapacity、loadFactor 创建 HashSet // 其实就是以相应的参数创建 HashMap public HashSet(int initialCapacity, float loadFactor) { map = new HashMap(initialCapacity, loadFactor); } public HashSet(int initialCapacity) { map = new HashMap(initialCapacity); } HashSet(int initialCapacity, float loadFactor, boolean dummy) { map = new LinkedHashMap(initialCapacity , loadFactor); } // 调用 map 的 keySet 来返回所有的 key public Iterator iterator() { return map.keySet().iterator(); } // 调用 HashMap 的 size() 方法返回 Entry 的数量,就得到该 Set 里元素的个数 public int size() { return map.size(); } // 调用 HashMap 的 isEmpty() 判断该 HashSet 是否为空, // 当 HashMap 为空时,对应的 HashSet 也为空 public boolean isEmpty() { return map.isEmpty(); } // 调用 HashMap 的 containsKey 判断是否包含指定 key //HashSet 的所有元素就是通过 HashMap 的 key 来保存的 public boolean contains(Object o) { return map.containsKey(o); } // 将指定元素放入 HashSet 中,也就是将该元素作为 key 放入 HashMap public boolean add(E e) { return map.put(e, PRESENT) == null; } // 调用 HashMap 的 remove 方法删除指定 Entry,也就删除了 HashSet 中对应的元素 public boolean remove(Object o) { return map.remove(o)==PRESENT; } // 调用 Map 的 clear 方法清空所有 Entry,也就清空了 HashSet 中所有元素 public void clear() { map.clear(); } ... }

由上面源程序可以看出,HashSet 的实现其实非常简单,它只是封装了一个 HashMap 对象来存储所有的集合元素,所有放入 HashSet 中的集合元素实际上由 HashMap 的 key 来保存,而 HashMap 的 value 则存储了一个 PRESENT,它是一个静态的 Object 对象。

HashSet 的绝大部分方法都是通过调用 HashMap 的方法来实现的,因此 HashSet 和 HashMap 两个集合在实现本质上是相同的。 HashMap 的 put 与 HashSet 的 add

由于 HashSet 的 add() 方法添加集合元素时实际上转变为调用 HashMap 的 put() 方法来添加 key-value 对,当新放入 HashMap 的 Entry 中 key 与集合中原有 Entry 的 key 相同(hashCode() 返回值相等,通过 equals 比较也返回 true),新添加的 Entry 的 value 将覆盖原来 Entry 的 value,但 key 不会有任何改变,因此如果向 HashSet 中添加一个已经存在的元素,新添加的集合元素(底层由 HashMap 的 key 保存)不会覆盖已有的集合元素。

掌握上面理论知识之后,接下来看一个示例程序,测试一下自己是否真正掌握了 HashMap 和 HashSet 集合的功能。 class Name { private String first; private String last; public Name(String first, String last) { this.first = first; this.last = last; } public boolean equals(Object o) { if (this == o) { return true; } if (o.getClass() == Name.class) { Name n = (Name)o; return n.first.equals(first) && n.last.equals(last); } return false; } } public class HashSetTest { public static void main(String[] args) { Set s = new HashSet(); s.add(new Name("abc", "123")); System.out.println( s.contains(new Name("abc", "123"))); } }

上面程序中向 HashSet 里添加了一个 new Name("abc", "123") 对象之后,立即通过程序判断该 HashSet 是否包含一个 new Name("abc", "123") 对象。粗看上去,很容易以为该程序会输出 true。

实际运行上面程序将看到程序输出 false,这是因为 HashSet 判断两个对象相等的标准除了要求通过 equals() 方法比较返回 true 之外,还要求两个对象的 hashCode() 返回值相等。而上面程序没有重写 Name 类的 hashCode() 方法,两个 Name 对象的 hashCode() 返回值并不相同,因此 HashSet 会把它们当成 2 个对象处理,因此程序返回 false。

由此可见,当我们试图把某个类的对象当成 HashMap 的 key,或试图将这个类的对象放入 HashSet 中保存时,重写该类的 equals(Object obj) 方法和 hashCode() 方法很重要,而且这两个方法的返回值必须保持一致:当该类的两个的 hashCode() 返回值相同时,它们通过 equals() 方法比较也应该返回 true。通常来说,所有参与计算 hashCode() 返回值的关键属性,都应该用于作为 equals() 比较的标准。 hashCode() 和 equals()

关于如何正确地重写某个类的 hashCode() 方法和 equals() 方法,请参考疯狂 Java 体系的《疯狂 Java 讲义》一书中相关内容。

如下程序就正确重写了 Name 类的 hashCode() 和 equals() 方法,程序如下: class Name { private String first; private String last; public Name(String first, String last) { this.first = first; this.last = last; } // 根据 first 判断两个 Name 是否相等 public boolean equals(Object o) { if (this == o) { return true; } if (o.getClass() == Name.class) { Name n = (Name)o; return n.first.equals(first); } return false; } // 根据 first 计算 Name 对象的 hashCode() 返回值 public int hashCode() { return first.hashCode(); } public String toString() { return "Name[first=" + first + ", last=" + last + "]"; } } public class HashSetTest2 { public static void main(String[] args) { HashSet set = new HashSet(); set.add(new Name("abc" , "123")); set.add(new Name("abc" , "456")); System.out.println(set); } }

上面程序中提供了一个 Name 类,该 Name 类重写了 equals() 和 toString() 两个方法,这两个方法都是根据 Name 类的 first 实例变量来判断的,当两个 Name 对象的 first 实例变量相等时,这两个 Name 对象的 hashCode() 返回值也相同,通过 equals() 比较也会返回 true。

程序主方法先将第一个 Name 对象添加到 HashSet 中,该 Name 对象的 first 实例变量值为"abc",接着程序再次试图将一个 first 为"abc"的 Name 对象添加到 HashSet 中,很明显,此时没法将新的 Name 对象添加到该 HashSet 中,因为此处试图添加的 Name 对象的 first 也是" abc",HashSet 会判断此处新增的 Name 对象与原有的 Name 对象相同,因此无法添加进入,程序在①号代码处输出 set 集合时将看到该集合里只包含一个 Name 对象,就是第一个、last 为"123"的 Name 对象。

参考资料

关于作者 李刚照片 李刚,从事 Java EE 应用开发近 10 年。曾任 LITEON 公司的 J2EE 技术主管,负责该公司的企业信息化平台的架构设计。曾任广州电信、广东龙泉科技等公司的技术培训教师。疯狂 Java 联盟(http://www.crazyit.org)站长。疯狂 Java 实训营创始人,疯狂 Java 体系图书作者,曾任东方标准广州中心软件教学总监,曾兼任广东技术师范学院计算机科学系的兼职副教授。国内知名IT技术作家,已出版《疯狂 Java 讲义》、《轻量级 Java EE 企业应用实战》、《疯狂 Ajax 讲义》、《Struts 2.1 权威指南》、《Ruby On Rails 敏捷开发最佳实践》等著作。

对本文的评价 太差! (1) 需提高 (2) 一般;尚可 (3) 好文章 (4) 真棒!(5) 建议?

IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。 关于 IBM 隐私条约 联系 IBM 使用条款