-
-
最近整理一下面试中hashMap会问到的几个知识点:
数组的特点:寻址容易,插入和删除困难。
链表的特点是:寻址困难,插入和删除容易。
ArrayList的底层实现就是通过动态数组来实现的,LinkedLIst底层实现就是通过链表来实现的,所以直接答出数组和链表的特点就ok
面试题: hashMap是怎样实现key-value这样键值对的保存?
HashMap中有一个内部类Entry,
1234567static
class
Entry<k,v>
implements
Map.Entry<k,v> {
final
K key;
V value;
Entry<k,v> next;
int
hash;
//.....
}</k,v></k,v></k,v>
面试题: hashMap的实现原理?
1:hashMap是基于哈希表的Map接口的一个非同步实现,并允许空值空键,此类并不保证映射的顺序,特别是它不保证顺序恒久不变。
2:hashMap的数据结构,其实是一个“链表散列”式的数据结构,即数组和链表的结合体。
3:hashMap的功能是通过key能够迅速的找到值。
按照散列函数的定义,如果两个对象相同,即obj1.equals(obj2)=true,则它们的hashCode必须相同,但如果两个对象不同,则它们的hashCode不一定不同。
如果两个不同对象的hashCode相同,这种现象称为冲突,冲突会导致操作哈希表的时间开销增大,所以尽量定义好的hashCode()方法,能加快哈希表的操作。
如果相同的对象有不同的hashCode,对哈希表的操作会出现意想不到的结果(期待的get方法返回null),
再回头看看前面提到的为什么覆盖了equals方法之后一定要覆盖hashCode方法,很简单,比如,String a = new String(“abc”);String b = new String(“abc”);如果不覆盖hashCode的话,那么a和b的hashCode就会不同,把这两个类当做key存到HashMap中的话就 会出现问题,就会和key的唯一性相矛盾。
HashMap使用到的数据类型主要就是数组和链表,首先看原理图
在hashMap的原理图中,左边是通过数组来存放链表的第一个节点,看懂这个图这个问题就ok
面试题: hashMap的put过程?
面我们提到过Entry类里面有一个next属性,作用是指向下一个Entry。比如说: 第一个键值对A进来,通过计算其key的hash得到的index=0,记做:Entry[0] = A。一会后又进来一个键值对B,通过计算其index也等于0,现在怎么办?HashMap会这样做:B.next = A,Entry[0] = B,如果又进来C,index也等于0,那么C.next = B,Entry[0] = C;这样我们发现index=0的地方其实存取了A,B,C三个键值对,他们通过next这个属性链接在一起。也就是说数组中存储的是最后插入的元素。
12345678910111213141516171819public
V put(K key, V value) {
if
(key ==
null
)
return
putForNullKey(value);
int
hash = hash(key);
int
i = indexFor(hash, table.length);
for
(Entry<k,v> e = table[i]; e !=
null
; e = e.next) {
//循环判断插入的key是否已经存在,若存在就更新key对应的value
Object k;
if
(e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(
this
);
return
oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
//key不存在,那么插入新的key-value
return
null
;
}</k,v>
123456789101112131415void
addEntry(
int
hash, K key, V value,
int
bucketIndex) {
if
((size >= threshold) && (
null
!= table[bucketIndex])) {
resize(
2
* table.length);
hash = (
null
!= key) ? hash(key) :
0
;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
void
createEntry(
int
hash, K key, V value,
int
bucketIndex) {
//这个方法就验证了上面说的<strong>数组中存储的是最后插入的元素</strong>
Entry<k,v> e = table[bucketIndex];
table[bucketIndex] =
new
Entry<>(hash, key, value, e);
size++;
}</k,v>
这个过程比较简单,直接看代码:1234567891011121314public
V get(Object key) {
if
(key ==
null
)
return
getForNullKey();
int
hash = hash(key.hashCode());
//先定位到数组元素,再遍历该元素处的链表
for
(Entry<k,v> e = table[indexFor(hash, table.length)];
e !=
null
;
e = e.next) {
Object k;
if
(e.hash == hash && ((k = e.key) == key || key.equals(k)))
return
e.value;
}
return
null
;
}</k,v>
找到indexFor这个方法,hashMap就是通过这个方法获取数组下标的:123static
int
indexFor(
int
h,
int
length) {
return
h & (length-
1
);
}
面试题: hashMap中数组的初始化大小过程?找到hashMap的构造方法:
12345678910public
HashMap(
int
initialCapacity,
float
loadFactor) {
.....
// Find a power of 2 >= initialCapacity
int
capacity =
1
;
while
(capacity < initialCapacity)
capacity <<=
1
;
//相当于capacity = capacity * 2
this
.loadFactor = loadFactor;
threshold = (
int
)(capacity * loadFactor);
table =
new
Entry[capacity];
init();
}
面试题: hashMap什么时候开始rehash?
在hashMap中有一个加载因子loadFactor,默认值是0.75,当数组的实际存入值的大小 > 数组的长度×loadFactor 时候就会rehash,重新创建一个新的表,将原表的映射到新表中,这个过程很费时。 -
1. HashMap概述
HashMap是基于哈希表的Map接口的非同步实现。此实现提供所有可选的映射操作,并允许使用null值和null键。此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
2. HashMap的数据结构在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。
3. HashMap的存取HashMap的功能是通过“键(key)”能够快速的找到“值”。下面我们分析下HashMap存数据的基本流程:
3.HashMap与Hashtable的区别:
HashMap可以接受null键值和值,而Hashtable则不能。
Hashtable是线程安全的,通过synchronized实现线程同步。而HashMap是非线程安全的,但是速度比Hashtable快。
4.如果两个键的hashcode相同,你如何获取值对象
HashMap在链表中存储的是键值对,找到哈希地址位置之后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象
5.如果HashMap的大小超过了负载因子(load factor)定义的容量,怎么办
HashMap默认的负载因子大小为0.75,也就是说,当一个map填满了75%的空间的时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的数组,来重新调整map的大小,并将原来的对象放入新的数组中。
6.为什么String, Interger这样的wrapper类适合作为键?
String, Interger这样的wrapper类是final类型的,具有不可变性,而且已经重写了equals()和hashCode()方法了。其他的wrapper类也有这个特点。不可变性是必要的,因为为了要计算hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的hashcode的话,那么就不能从HashMap中找到你想要的对象。
7.ConcurrentHashMap和Hashtable的区别
Hashtable和ConcurrentHashMap有什么分别呢?它们都可以用于多线程的环境,但是当Hashtable的大小增加到一定的时候,性能会急剧下降,因为迭代时需要被锁定很长的时间。因为ConcurrentHashMap引入了分割(segmentation),不论它变得多么大,仅仅需要锁定map的某个部分,而其它的线程不需要等到迭代完成才能访问map。简而言之,在迭代的过程中,ConcurrentHashMap仅仅锁定map的某个部分,而Hashtable则会锁定整个map。
8.HashMap的遍历
第一种:
Map map = new HashMap();
Iterator iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
Object key = entry.getKey();
Object val = entry.getValue();
}
效率高,以后一定要使用此种方式!
第二种:
Map map = new HashMap();
Iterator iter = map.keySet().iterator();
while (iter.hasNext()) {
Object key = iter.next();
Object val = map.get(key);
}
效率低,以后尽量少使用!可是为什么第一种比第二种方法效率更高呢?
HashMap这两种遍历方法是分别对keyset及entryset来进行遍历,但是对于keySet其实是遍历了2次,一次是转为iterator,一次就从hashmap中取出key所对于的value。而entryset只是遍历了第一次,它把key和value都放到了entry中,即键值对,所以就快了。
先总结到这吧,以后遇到有价值的hashmap相关的题目日后再做更新,文章不足之处,反应各位拍砖指正。。。。
-
-
-
-
java中hashMap有关的面试题
最新推荐文章于 2024-09-21 16:09:41 发布