Java面试题6

Java面试题6

51.HashMap的实现原理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
HashMap的主干是一个Entry数组。
Entry是HashMap的基本组成单元,
每一个Entry包含一个key-value键值对。

HashMap基于hashing原理,
我们通过put()和get()方法储存和获取对象。
当我们将键值对传递给put()方法时,
它调用键对象的hashCode()方法
来计算hashcode,
让后找到bucket位置来储存值对象。
当获取对象时,
通过键对象的equals()方法
找到正确的键值对,
然后返回值对象。
HashMap使用链表来解决碰撞问题,
当发生碰撞了,
对象将会储存在链表的下一个节点中。
HashMap在每个链表节点中储存键值对对象。

当两个不同的键对象的hashcode
相同时会发生什么?
它们会储存在同一个bucket位置的链表中。
键对象的equals()方法用来找到键值对。

因为HashMap的好处非常多,
我曾经在电子商务的应用中使用HashMap作为缓存。
因为金融领域非常多的运用Java,
也出于性能的考虑,
我们会经常用到HashMap和ConcurrentHashMap。


HashMap由数组+链表组成的,
数组是HashMap的主体,
链表则是主要为了解决哈希冲突而存在的,
如果定位到的数组位置不含链表
当前entry的next指向null,
那么对于查找,
添加等操作很快,
仅需一次寻址即可;
如果定位到的数组包含链表,
对于添加操作,
其时间复杂度为O(n),
首先遍历链表,
存在即覆盖,
否则新增;
对于查找操作来讲,
仍需遍历链表,
然后通过key对象的equals方法逐一比对查找。
所以,性能考虑,HashMap中的链表出现越少,
性能才会越好。
52.List、Set、Map之间的区别
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
List、Set是实现了Collection接口的子接口;
而Map是另一个集合接口。1元素重复性:
List允许有重复的元素。
任何数量的重复元素
都可以在不影响现有重复元素的值
及其索引的情况下插入到List集合中;
Set集合不允许元素重复。
Set以及所有实现了Set接口的类
都不允许重复值的插入,
若多次插入同一个元素时,
在该集合中只显示一个;
Map以键值对的形式对元素进行存储。
Map不允许有重复键,
但允许有不同键对应的重复的值;
2.元素的有序性:

List及其所有实现类保持了
每个元素的插入顺序;
Set中的元素都是无序的;
但是某些Set的实现类
以某种殊形式对其中的元素进行排序,
如:LinkedHashSet按照元素的
插入顺序进行排序;
Map跟Set一样对元素进行无序存储,
但其某些实现类对元素进行了排序。
如:TreeMap根据键对其中的元素进行升序排序;
3.元素是否为空值:
1.List允许任意数量的空值;
2.Set最多允许一个空值的出现;
当向Set集合中添加多个null值时,
在该Set集合中只会显示一个null元素
3.Map只允许出现一个空键,
但允许出现任意数量的空值;
---------------------------------
总结:
List中的元素:
有序、可重复、可为空;Set中的元素:
无序、不重复、只有一个空元素;Map中的元素:
无序、键不重,值可重、可一个空键、多可空值;
53.HashMap 的长度为什么是2的幂次方
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
1.减小哈希冲突概率 
假如当前Entry数组长度为len,
插入节点时,
需要对key的hashcode进行二次哈希,
然后跟len-1相与
得到的值一定小于len,避免数组越界

如果len是2的N次方,
那么len-1的后N位二进制一定是全1

假设有两个key,
他们的hashcode不同,
分别为code1和code2
code1和code2分别与一个后N位全1的二进制相与,
结果一定也不同
但是,如果code1和code2分别
与一个后N位非全1的二进制相与,
结果有可能相同

也就是说,
如果len是2^N,
不同hashcode的key
计算出来的数组下标一定不同;
否则,
不同hashcode的key
计算出来的数组下标一定相同。

所以HashMap长度为全1,
可以减小哈希冲突概率。
----------------------
2.提高计算下标的效率
如果len的二进制后n位非全1,
与len-1相与时,
0与1相与需要取反。
如果len的二进制后n位全1,
完全不需要取反。

如果len为2^N,
那么与len-1相与,
跟取余len等价,
而与运算效率高于取余。
如果len不是2^N,
与len-1相与,
跟取余len不等价。
54.集合框架中的泛型有什么优点?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
首先,
了解一下Java关于泛型的概念。
泛型,在C++中被称为模板,
就是一种抽象的编程方式。
当我们定义类和方法的时候,
可以用一种通用的方式进行定义,
而不必写出具体的类,
这些未知的东西会在真正使用的时候在确定。

对于集合类来说,
它们可以存放各种类型的元素。
如果在存放之前,
就能确定元素的类型,
那么就可以更加直观,
也让代码更加简洁。

说明:
java的泛型是停留在编译层的,
也就是说JVM在对待泛型数据的时候,
依然会把它们看成是Object类型,
只不过在使用这些元素的时候,
JVM会自动帮助我们进行相应的类型转换。

总结:
集合使用泛型之后,
可以达到元素类型明确的目的,
避免了手动类型转换的过程,
同时,也让我们更加明确
容器保存的是什么类型的数据。
55.我们能否使用任何类作为Map的key?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1、可以 
但是做为key的数据有如下要求:
2、首先,
要求明确一点Map集合存储数据的
主要目的是为了查找
而List集合是为了输出
3、既然是查找那么就要涉及到对象比较
我们说了如果要进行对象比较
就必须覆写Object类中的equals()、hasCode()
至少覆写equals()方法 简单理解:
自己定义的类如果要想实现对象比较
就必须至少覆写equals()方法
4、或则这么说只要是自己定义的类
要想将其作为key
就必须覆写equals()方法
5、实际工作中 key的类型一定是String型
(95%通用) 其余的5%是没事找事的
6、按标准开发、你会感到事半功倍,
不要没事给自己找事,
当然求知精神是值得肯定的。
56.Map接口提供了哪些不同的集合视图?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
Map接口提供了三个集合视图:
1.Set keyset():
返回map中包含的所有key的一个Set视图。
集合是受map支持的,
map的变化会在集合中反映出来,
反之亦然。
当一个迭代器正在遍历一个集合时,
若map被修改了(除迭代器自身的移除操作以外),
迭代器的结果会变为未定义。
集合支持通过
Iterator的Remove、Set.remove、
removeAll、
retainAll和clear操作进行元素移除,
从map中移除对应的映射。
它不支持add和addAll操作。
2.Collection values():
返回一个map中包含的
所有value的一个Collection视图。
这个collection受map支持的,
map的变化会在collection中反映出来,
反之亦然。
当一个迭代器正在遍历一个collection时,
若map被修改了(除迭代器自身的移除操作以外),
迭代器的结果会变为未定义。
集合支持通过
Iterator的Remove、Set.remove、
removeAll、
retainAll和clear操作进行元素移除,
从map中移除对应的映射。

它不支持add和addAll操作。
3.Set> entrySet():
返回一个map钟包含的
所有映射的一个集合视图。
这个集合受map支持的,
map的变化会在collection中反映出来,
反之亦然。
当一个迭代器正在遍历一个集合时,
若map被修改了
除迭代器自身的移除操作,
以及对迭代器返回的entry进行setValue外,
迭代器的结果会变为未定义。
集合支持通过
Iterator的Remove、Set.remove、
removeAll、
retainAll和clear操作进行元素移除,
从map中移除对应的映射。
它不支持add和addAll操作。
57.哪些集合类是线程安全的?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
在集合框架中,有些类是线程安全的,
这些都是jdk1.1中的出现的。
在jdk1.2之后,
就出现许许多多非线程安全的类。
下面是这些线程安全的同步的类:vector:
就比arraylist多了个同步化机制(线程安全),
因为效率较低,
现在已经不太建议使用。
在web应用中,
特别是前台页面,
往往效率(页面响应速度)是优先考虑的。
statck:堆栈类,先进后出
hashtable:就比hashmap多了个线程安全
enumeration:枚举,相当于迭代器

除了这些之外,
其他的都是非线程安全的类和接口。

线程安全的类其方法是同步的,
每次只能一个访问。
是重量级对象,
效率较低。
58.队列和栈是什么,列出它们的区别?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
队列(Queue):
是限定只能在表的一端进行
插入和在另一端进行删除操作的线性表;

栈(Stack):
是限定只能在表的一端
进行插入和删除操作的线性表。

区别如下:
一、规则不同
1. 队列:先进先出(First In First Out)FIFO
2. 栈:先进后出(First In Last Out )FILO

二、对插入和删除操作的限定不同
1. 队列:只能在表的一端进行插入,
并在表的另一端进行删除;
2. 栈:只能在表的一端插入和删除。

三、遍历数据速度不同
1. 队列:基于地址指针进行遍历,
而且可以从头部或者尾部进行遍历,
但不能同时遍历,
无需开辟空间,
因为在遍历的过程中不影响数据结构,
所以遍历速度要快;

2. 栈:只能从顶部取数据,
也就是说最先进入栈底的,
需要遍历整个栈才能取出来,
而且在遍历数据的同时需要
为数据开辟临时空间,
保持数据在遍历前的一致性。
59.哪一个List实现了最快插入?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
LinkedList和ArrayList
是另个不同变量列表的实现。
ArrayList的优势在于动态的增长数组,
非常适合初始时总长度未知的情况下使用。
LinkedList的优势在于在中间位置插入和删除操作,
速度是最快的。

LinkedList实现了List接口,
允许null元素。
此外LinkedList提供额外的
get,remove,insert方法
在LinkedList的首部或尾部。
这些操作使LinkedList可被
用作堆栈(stack),
队列(queue)
或双向队列(deque)。

ArrayList实现了可变大小的数组。
它允许所有元素,
包括null。
每个ArrayList实例都有一个容量(Capacity),
即用于存储元素的数组的大小。
这个容量可随着不断添加新元素而自动增加,
但是增长算法并没有定义。
当需要插入大量元素时,
在插入前可以调用ensureCapacity方法来
增加ArrayList的容量以提高插入效率。
60.什么时候使用ConcurrentHashMap?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
快速失败的Java迭代器
可能会引发ConcurrentModifcationException
在底层集合迭代过程中被修改。
故障安全作为发生在实例中的一个副本
迭代是不会抛出任何异常的。
快速失败的故障安全范例定义了
当遭遇故障时系统是如何反应的。
例如,用于失败的快速迭代器ArrayList
和用于故障安全的迭代器ConcurrentHashMap。

ConcurrentHashMap被作为
故障安全迭代器的一个实例,
它允许完整的并发检索和更新。
当有大量的并发更新时,
ConcurrentHashMap此时可以被使用。
这非常类似于Hashtable,
但ConcurrentHashMap不锁定
整个表来提供并发,
所以从这点上ConcurrentHashMap的性能
似乎更好一些。
所以当有大量更新时
ConcurrentHashMap应该被使用。
-------------本文结束感谢您的阅读-------------
0%