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