JDK1.8以前的HashMap
原來的hashMap採用的數據結構是哈希表(數組+鏈表),hashMap底層是Entry類型的,名字叫做table的數組,當new一個hsahMap的時候會構造一個容量爲16,負載因子爲0.75的空hashMap,它的吞吐臨界值爲16 * 0.75 = 12。當添加達到吞吐臨界值的時候會用resize的方法2倍擴容,並且通過transfer()將原來的值放入新的Entry數組中
當執行put命令將key-value鍵值對放入hashMap中的時候,程序首先根據key的hashCode()返回值決定在Entry數組中的存儲位置,如果此位置爲空則直接保存,若不爲空,HashCode肯定相等,就會通過key的equals進行比較,若返回true,新添加的value將覆蓋集合中原來的value,若返回false,新添加的Entry將與集合中原有的Entry形成Entry鏈,而新添加的Entry位於Entry鏈的頭部,在某些情況下如果鏈表 無限下去,那麼效率極低,碰撞是避免不了的
JDK1.8以後的HashMap
在1.8之後,在數組+鏈表+紅黑樹來實現hashmap,當碰撞的元素個數大於8時 & 總容量大於64,會有紅黑樹的引入
除了添加之後,效率都比鏈表高,1.8之後鏈表新進元素加到末尾
ConcurrentHashMap (鎖分段機制),concurrentLevel,jdk1.8採用CAS算法(無鎖算法,不再使用鎖分段),數組+鏈表中也引入了紅黑樹的使用
hashCode()與equals()的相關規定:
- 如果兩個對象相等,則hashcode一定也是相同的
- 如果兩個對象相等,對兩個equals方法返回true
- 兩個對象有相同的hashcode值,它們也不一定是相等的
- 綜上,equals方法被覆蓋過,則hashCode方法也必須被覆蓋(否則HashMap put數據時,key值無法確定是否爲同一對象)
兩個對象hashCode()相同,equals() 也一定爲 true嗎?
首先,答案肯定是不一定。同時反過來equals爲true,hashCode也不一定相同。
類的hashCode方法和equals方法都可以重寫,返回的值完全在於自己定義。
hashCode()返回該對象的哈希碼值;equals()返回兩個對象是否相等。
關於hashCode和equal是方法是有一些 常規協定 :
1、兩個對象用equals()比較返回true,那麼兩個對象的hashCode()方法必須返回相同的結果。
2、兩個對象用equals()比較返回false,不要求hashCode()方法也一定返回不同的值,但最好返回不同值,提搞哈希表性能。
3、重寫equals()方法,必須重寫hashCode()方法,以保證equals方法相等時兩個對象hashcode返回相同的值。
兩個個關於這兩個方法的重要規範(我只是抽取了最重要的兩個,其實不止兩個):
規範1:若重寫equals(Object obj)方法,有必要重寫hashcode()方法,確保通過equals(Object obj)方法判斷結果爲true的兩個對象具備相等的hashcode()返回值。
說得簡單點就是:“如果兩個對象相同,那麼他們的hashcode應該 相等”。
不過請注意:這個只是規範,如果你非要寫一個類讓equals(Object obj)返回true而hashcode()返回兩個不相等的值,編譯和運行都是不會報錯的。不過這樣違反了Java規範,程序也就埋下了BUG。
規範2:如果equals(Object obj)返回false,即兩個對象“不相同”,並不要求對這兩個對象調用hashcode()方法得到兩個不相同的數。
說的簡單點就是:“如果兩個對象不相同,他們的hashcode可能相同”。 根據這兩個規範,
可以得到如下推論:
1、如果兩個對象equals=true,Java運行時環境會認爲他們的hashcode一定相等。
2、如果兩個對象不equals=true,他們的hashcode有可能相等。
3、如果兩個對象hashcode相等,他們不一定equals=true。
4、如果兩個對象hashcode不相等,他們一定不equals=true。
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Student student = (Student) o;
if (age != student.age) return false;
return name.equals(student.name);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + age;
return result;
}
}
如果兩個對象equals=true,Java運行時環境會認爲他們的hashcode一定相等。
public static void main(String[] args){
Student str1 = new Student("dd", 29);
Student str2 = new Student("dd", 29);
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
System.out.println(str1.hashCode() == str2.hashCode());
System.out.println(str1.equals(str2));
}
// 99229
// 99229
// true
// true
如果兩個對象hashcode相等,他們不一定equals=true。
public static void main(String[] args){
Student str1 = new Student("dd1", 29);
Student str2 = new Student("dd", 2984548);
System.out.println(str1.hashCode());
System.out.println(str2.hashCode());
System.out.println(str1.hashCode() == str2.hashCode());
System.out.println(str1.equals(str2));
}
// 3076748
// 3083748
// false
// false
1.7之前字符串常量池是放在永久代的,1.7把字符串常量池從永久代中剝離出來,存放在堆空間中。
JDK1.8以後的jvm
JDK1.8 : 1.8同1.7比,最大的差別就是:元數據區取代了永久代。
元空間的本質和永久代類似,都是對JVM規範中方法區的實現。不過元空間與永久代之間最大的區別在於:元數據空間並不在虛擬機中,而是使用本地內存。
年輕代 : 三分之一的堆空間
eden區: 8/10 的年輕代空間
survivor0 : 1/10 的年輕代空間
survivor1 : 1/10 的年輕代空間
老年代 : 三分之二的堆空間
如果服務器內存足夠,升級到 JDK 1.8 修改 JVM 參數最簡單的辦法就是將 -XX:PermSize 和 -XX:MaxPermSize 參數替換爲 -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize
1.8中-XX:PermSize 和 -XX:MaxPermSize 已經失效,取而代之的是一個新的區域 —— Metaspace(元數據區)。
使用JDK1.8以及之後的版本,不會再碰上“java.lang.OutOfMemoryError: PermGen space”這個錯誤了
-XX:MaxMetaspaceSize=128m
設置最大的元內存空間128兆
注意:如果不設置JVM將會根據一定的策略自動增加
本地元內存空間。
如果你設置的元內存空間過小,你的應用程序可能得到:java.lang.OutOfMemoryError: Metadata space