HashMap相关问题(底层原理,线程安全,如何保证有序,初始默认长度,构建线程安全的map,工作原理,大小超过负载因子容量,hashcode的作用)

HashMap的底层原理是什么

  • hashmap内部是一个数组+链表的数据结构,在put元素时,首先会把元素的key做hash运算,在通过这个hash值与数组长度做位运算,在通过头插法将元素插入到链表,get的时候就用相同的方法得到元素的下标,如果该数组下标只有一个元素,则该元素就是map的value,如果是链表结构,就循环链表得到map的value

HashMap的底层原理数组长度为何总是2的n次方?

  • HashMap根据用户传入的初始化容量,利用无符号右移和按位或运算等方式计算出第一个大于该数的2的幂
    • 使数据分部均匀,减少碰撞
    • 当length为2的n次方时,h&(length - 1)就相当于对length取模,而且在速度、效率上比直接取模要快的多

hashmap是否是线程安全的

  • 非线程安全的,因为如果在高并发下,会导致在某个index下有循环链表的产生,这时如果get一个不存在的key,该key计算出的index值正好等于前面循环链表所在的index值,就会出现死循环

LinkedHashMap和TreeMap是怎么保证有序的?

  • LinkedHashMap继承自HashMap内部维护了一个链表来保持它内部元素的插入顺序,迭代的时候也是按照插入的顺序迭代;
  • TreeMap则是按照key的自然顺序来排序,如果需要可以集成Comparator接口来实现

hashMap默认初始长度是多少?为什么这么规定?

  • 默认初始长度是16,为了服务于key映射到index的hash算法,该算法是hash(key)&length-1,位运算可以保证性能,并且效果和取模一样,16-1是为了保证其二进制位数都是1,这样只要hash函数算出的结果是均匀的,得到的index也是均匀的

如果构建一个线程安全的Map?

  • 可以用hashTable和currentHashMap,HashTable就是实现了synchronize关键字的hashmap,效率很低,一般不建议使用。put的时候则由key得到hash值,由这个hash值定位到所在的segment,获得可重入锁,在通过hash值获取具体的index,插入或者覆盖对象,释放锁。
  • get的时候则由key得到hash值,由这个hash值定位到所在的segment,再定位到具体的index。java8则该用synchonize和CAS来实现,同时底层该用链表+红黑树的结构,存放数据由hashEntry改为Node,并且由volatile关键字修饰value和next来保证其多线程下的可见性
  • put的时候计算key的hash值,判断是否需要初始化,定位出的node如果为空,则利用CAS插入数据,如果hashCodeMOVIED-1则扩容,否则领suchronize锁写入数据,get的时候依然通过hashcode寻址,如果是链表按照链表方式寻址,如果是红黑树则按照红黑树凡是寻址

HashMap的工作原理与HashMap的get()方法的工作原理

  • HashMap是基于hashing的原理,我们是用put(key,value)存储对象到HashMap中,使用get(key)从HashMap中获得对象。当我们给put()方法传递键和值时,我们先对键调用HashCode()方法,返回的hashcode用于寻找到bucket位置来储存Entry对象。HashMap是在bucket中储存键对象和值对象,作为Map.Entry

当两个对象的hashcode相同会发生什么?

  • hashcode相同,所以两个对象时相等的,HashMap将会抛出异常,但是他们可能并不相等是因为hashcode相同,所以他们的bucket位置相同,“碰撞”会发生,因为HashMap使用链表存储对象,这个Entry会存储在链表中

可以使用ConcurrentHashMap来代替Hashtable吗?

  • 因为ConcurrentHashMap越来越多的人用了,我们知道Hashtable是synchronized的,但是ConcurrentHashMap同步性能更好,因为它仅仅根据同步级对map的一部分进行上锁,ConcurrentHashMap当然可以代替HashTable,但是HashTable提供更强的线程安全性

为什么String、Interger的wrapper类适合作为键?

  • String、Interger这样的wrapper类作为HashMap的键是再合适不过了,而且String最为常用。因为String是不可变的,也是final的,而且已经重写了equals()和hashCode()方法了,其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其他的优点如线程安全。因为获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的hashcode的话,那么碰撞的几率就会小些,这样就能提高HashMap的性能

如果HashMap的大小超过了负载因子定义的容量,怎么办?

  • 默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其他集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫做rehashing,因为它调用hash方法找到新的bucket位置

HashMap的hashcode的作用

  • HashCode的特性

(1)HashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等,HashCode经常用于确定对象的存储地址。

(2)如果两个对象相同,equals方法一定返回true,并且这两个对象的HashCode一定相同。

(3)两个对象的HashCode相同,并不一定表示两个对象就相同,即equals()不一定为true,只能够说明这两个对象在一个散列存储结构中。

(4)如果对象的equals方法被重写,那么对象的HashCode也尽量重写

  • HashCode作用

java中的结合有两类,一类是List,再有一类是Set。前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。

equals方法可用于保证元素不重复,但如果每增加一个元素就检查一次,若集合中现在已经有1000个元素,那么第1001个元素加入集合是,就要调用1000次equals方法。这显然会大大降低效率。于是,java采用了哈希表原理。

哈希算法也被称为散列算法,是将数据依特定算法直接指定到一个地址上。

这样一来,当集合要添加新的元素时,先调用这个元素的HashCode方法,就一下子能定位到它应该放置的物理位置上。

(1)如果这个位置上没有元素,它就可以直接存储在这个位置行,不准在进行任何比较了。
(2)如果这个位置上已经有元素了,就调用他的equals方法与新元素进行比较,相同的话就不存了。
(3)不相同的话,也就是发生了Hash key相同导致冲突的情况,那么就在这个Hash key的地方产生一个链表,将所有产生相同HashCode的对象放到这个单链表上去,串在一起(很少出现)

这样一来实际调用equals方法的次数就大大降低了,几乎只用一两次。

hashCode的存在主要是用于查找的快捷性,如Hashtable,HashMap等。hashCode是用来在散列存储结构中确定对象的存储地址的。

如果两个对象相同,就是适用于equals(java.lang.Objct)方法,那么这两个对象的hashCode使用的对象,一定要和equals方法中使用的一致,否则就会违反上面提到的第二点。

两个对象的hashCode相同,并不一定表示两个相同就相同,也就是不一定适用于equals(java.lang.Object)方法,只能够说明这两个对象在散列存储结构中,如果Hashtable,他们“存放在同一个篮子里”。

什么时候需要重写?

一般的地方不需要重载hashCode,只有当类需要放在HashTable、HashMap、HashSet等等hash结构的集合时才会重载hashCode,那么为什么要重载hashCode呢?

要比较两个类的内容属性值,是否相同时,根据hashCode重写规则,重写类的值指定字段的hashCode(),equals()方法。
例如:

public class EmpWorkCondition{
	
	/**
	*员工ID
	*/
	private Integer empId;
	/**
	*员工服务总单数
	*/
	private Integer orderSum;
	@Override
	public boolean equals(Object o){
		if(this == o){
			return true;
		}
		if(o == null || getClass()!= o.getClass()){
			return false;
		}
		EmpWorkConditon that = (EmpWorkCondition) o;
		return Objects.equals(empId,that.empId);
	}
	@Override
	public int hashCode(){
		return Object.hash(empId);
	}
	//省略 getter setter
}
public static void main(String[] args){
	
	List<EmpWorkCondition> list = new ArrayList<EmpWorkCondition>();

	EmpWorkCondition emp1 = new EmpWorkCondition();
	emp1.setEmpId(100);
	emp1.setOrderSum(90000);
	list1.add(emp1);
	
	List<empWorkCondition> list2 = new ArraryList<EmpWorkCondition>();

	EmpWorkCondition emp2 = new EmpWorkCondition();
	emp2.setEmpId(100);
	emp2.setOrderSum(90000);
	list2.add(emp2);
	
	System.out.println(list1.contains(emp2));
}

输出结果:true
上面的方法,做的事情就是比较两个集合中的,实体类对象属性值,是否一致

OrderSum不在比较范围内,因为没有重写它,equals()和hashCode()方法为什么要超载equal方法?

因为Objcet的equal方法默认是两个对象的引用的比较,意思就是指向统一内存,地址则相等,否则不相等,如果你现在需要利用对象里面的值来判断是否相等,则重载equal方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值