- HashMap与HashTable区别以及HashMap的底层实现。
- HashTable:synchronized比HashMap线程安全,但是不能接受null值,而HashMap可以接受为null的键和值(当然只能有一个key为null),性能方面:HashTable是线程安全的,那么在单线程下,性能肯定比不过HashMap;另一个区别是HashMap的迭代器是fail-fast即快速失败迭代器
- 快速失败迭代器?
- 能否让HashMap同步?
Hash可以使用以下语句进行同步:Map m=Collections.synchronizedMap(hashMap)
- HashMap的底层实现:----》由HashMap数据结构说到底层实现
首先回答介绍HashMap是什么及他的特点:例如实现了Map结构,以键值对的方式存放数据,线程不安全,key和value可以为空等特点。
然后从HashMap的数据结构分析:即HashMap是由数组和链表来实现对数据的存储。也就是哈希表(所知道哈希表的实现方法:拉链发和开放定值法)。
然后回答通过HashMap的put的get方法具体说明HashMap的低层实现。
Put:HashMap会对null值key进行特殊处理,总是放到table[0]位置
put过程是先计算hash然后通过hash与table.length取摸计算index值,然后将key放到table[index]位置,当table[index]已存在其它元素时,会在table[index]位置形成一个链表,将新添加的元素放在table[index],原来的元素通过Entry的next进行链接,这样以链表形式解决hash冲突问题,当元素数量达到临界值(capactiy*factor)时,则进行扩容,是table数组长度变为table.length*2
Get:同样当key为null时会进行特殊处理,在table[0]的链表上查找key为null的元素
get的过程是先计算hash然后通过hash与table.length取摸计算index值,然后遍历table[index]上的链表,直到找到key,然后返回
- ConcurrentHashMap原理分析。(未完待续)
由于a、HashMap线程不安全:(多线程情况下,HashMap使用put会引起死循环,导致CPU利用率接近100%,所以在并发情况下不能使用)b、虽然HashTable使用了Synchronized来保证线程安全,单在线程激烈时效率低下,多个线程进行put和get时,可能会进入阻塞或者轮循状态。(原因是所有访问HashTable的线程都必须竞争同一把锁)。
因此ConcurrentHashMap使用了锁分段技术:将数据分成一段一段的存储,然后给每一段数据都配一把锁,当一个线程占用锁访问一个数据段的时候,其他段的数据也能被其他线程访问。但是有些方法需要跨段:比如size()和containsValue(),他们可能需要锁定整个表而不是整个段,这需要按顺序锁定所有段,操作完毕后,又按顺序释放所有段的锁。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成,Segment是一种可重入锁ReentrantLock,他扮演锁的结构,HashEntry则用于存放键值对数据。一个ConcurrentHashMap包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构。一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素, 每个Segment守护者一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
源码分析:
- ArrayList和LinkedList,HashSet底层
ArrayList通过数组实现,默认数组初始化长度为10,add方法如果增加的元素超过10个,那么会生成一个新的数组,长度为原数组的1.5倍+1,然后将原数组的内容复制到新数组当中,后续添加的新内容都会放到新数组中。扩容数组调用的方法为Arrays.copyOf();
LinkedList::低层实现是基于双向循环链表,且头结点不存放数据,然后实现其增删改查操作,和数据结构中链表的增删改查完全相同,而且插入是有序的。
HashSet:的底层实现是通过Map来实现的,Set中不允许有重复的元素,类似于集合,在HashSet的实现的时候,通过Map来实现,每次往Set里添加数据,都会将数据设置为Map的键值,Map的值设置一个默认值,因为Map的键值不能重复,所以每次添加到Set内的数据也不能重复。
- 为什么重写equals必须重写Hashcode
在A.equals(B)返回true的情况下,A, B 的hashCode()要返回相同的值.我们在定义类时,我们经常会希望两个不同对象的某些属性值相同时就认为他们相同,所以我们要重写equals()方法,按照原则,我们重写了equals()方法,也要重写hashCode()方法,要保证上面所述的b,c原则;所以java中的很多类都重写了这两个方法,例如String类,包装类
自定义的一个类,想要把它的实例保存在集合中时,我们就需要重写这两个方法;
- ****Vector如何高效初始化
一般来说可以像数组那样遍历,一个一个赋值。
- Hashmap的初始容量可以自己定义吗?扩容?(resize)有什么要求?
元素个数大于当前数组的长度乘以加载因子时(0.75/可以修改)就会自动扩容,容量为2次幂(我们正常的思维是进行一个%运算,但是%运算效率太过低下,所以采用2进制运算,同时为了保证2进制的情况下进行一个均匀分布,所以把容量设置成2的幂,)。resize就是重新计算容量,resize方法:非常重要的hashmap扩容,先创建一个容量为table.length*2的新table,修改临界值,然后把table里面元素计算hash值并使用hash与table.length*2重新计算index放入到新的table里面
这里需要注意下是用每个元素的hash全部重新计算index,而不是简单的把原table对应index位置元素简单的移动到新table对应位置。
- 怎样减少hash碰撞
- 开放定值法
- 再哈希法:遇到冲突时,使用多个哈希函数计算地址,直到无冲突。
- ****链地址法(拉链法)----》优点和缺点
- 建立一个公共溢出区:另外设立存储空间用以存储发生冲突的记录。
- 数组为什么是连续存储的
用C里面的知识解决。
- ****HashMap在并发情况下会出现什么问题?
- 在多线程put后可能导致get无限循环,具体表现为COU使用率100%,
- 可能产生元素丢失的现象
- HashTable和ConcurrentHashMap等都是线程安全的,但HashTable是由synchronized实现的,效率低,因此常用ConcurrentHashMap,锁分段技术。(原理见第三问)2.使用synchronizedMap同步方法包装HashMap object,得到线程安全的Map,并在Map上进行操作。
- 哪些情况下用栈
栈:一种数据结构,先进后出。
应用:在CPU内部有提供栈这个机制,用于函数调用和返回,数字转字符,表达式求值,走迷宫等。在CPU内部栈中主要用来进行子程序调用和返回,中断时数据保存和返回。
- 练习遍历HashMap以及队列和栈的实现代码?
- 迭代器
Iterator:只能正向遍历集合,适用于获取移除元素。
使用注意事项:使用容器的Iterator()方法返回一个Iterator,然后通过Iterator的next()方法返回第一个元素,使用Iterator的hasNext()方法判断容器中是否还有元素,如果有,用next()方法获取下一个元素,可以用remove()删除迭代器返回的元素。
ListIterator继承自Iterator,专门针对List,可以从两个方向遍历List,同时支持元素的修改。
14.HashMap的快速失败

浙公网安备 33010602011771号