撩课-Java每天10道面试题第6天
51.HashMap的实现原理
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之间的区别
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.减小哈希冲突概率 假如当前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.集合框架中的泛型有什么优点?
首先, 了解一下Java关于泛型的概念。 泛型,在C++中被称为模板, 就是一种抽象的编程方式。 当我们定义类和方法的时候, 可以用一种通用的方式进行定义, 而不必写出具体的类, 这些未知的东西会在真正使用的时候在确定。 对于集合类来说, 它们可以存放各种类型的元素。 如果在存放之前, 就能确定元素的类型, 那么就可以更加直观, 也让代码更加简洁。 说明: java的泛型是停留在编译层的, 也就是说JVM在对待泛型数据的时候, 依然会把它们看成是Object类型, 只不过在使用这些元素的时候, JVM会自动帮助我们进行相应的类型转换。 总结: 集合使用泛型之后, 可以达到元素类型明确的目的, 避免了手动类型转换的过程, 同时,也让我们更加明确 容器保存的是什么类型的数据。
55.我们能否使用任何类作为Map的key?
1、可以 但是做为key的数据有如下要求: 2、首先, 要求明确一点Map集合存储数据的 主要目的是为了查找 而List集合是为了输出 3、既然是查找那么就要涉及到对象比较 我们说了如果要进行对象比较 就必须覆写Object类中的equals()、hasCode() 至少覆写equals()方法 简单理解: 自己定义的类如果要想实现对象比较 就必须至少覆写equals()方法 4、或则这么说只要是自己定义的类 要想将其作为key 就必须覆写equals()方法 5、实际工作中 key的类型一定是String型 (95%通用) 其余的5%是没事找事的 6、按标准开发、你会感到事半功倍, 不要没事给自己找事, 当然求知精神是值得肯定的。
56.Map接口提供了哪些不同的集合视图?
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.哪些集合类是线程安全的?
在集合框架中,有些类是线程安全的,
这些都是jdk1.1中的出现的。
在jdk1.2之后,
就出现许许多多非线程安全的类。
下面是这些线程安全的同步的类:
vector:
就比arraylist多了个同步化机制(线程安全),
因为效率较低,
现在已经不太建议使用。
在web应用中,
特别是前台页面,
往往效率(页面响应速度)是优先考虑的。
statck:堆栈类,先进后出
hashtable:就比hashmap多了个线程安全
enumeration:枚举,相当于迭代器
除了这些之外,
其他的都是非线程安全的类和接口。
线程安全的类其方法是同步的,
每次只能一个访问。
是重量级对象,
效率较低。
58.队列和栈是什么,列出它们的区别?
队列(Queue): 是限定只能在表的一端进行 插入和在另一端进行删除操作的线性表; 栈(Stack): 是限定只能在表的一端 进行插入和删除操作的线性表。 区别如下: 一、规则不同 1. 队列:先进先出(First In First Out)FIFO 2. 栈:先进后出(First In Last Out )FILO 二、对插入和删除操作的限定不同 1. 队列:只能在表的一端进行插入, 并在表的另一端进行删除; 2. 栈:只能在表的一端插入和删除。 三、遍历数据速度不同 1. 队列:基于地址指针进行遍历, 而且可以从头部或者尾部进行遍历, 但不能同时遍历, 无需开辟空间, 因为在遍历的过程中不影响数据结构, 所以遍历速度要快; 2. 栈:只能从顶部取数据, 也就是说最先进入栈底的, 需要遍历整个栈才能取出来, 而且在遍历数据的同时需要 为数据开辟临时空间, 保持数据在遍历前的一致性。
59.哪一个List实现了最快插入?
LinkedList和ArrayList
是另个不同变量列表的实现。
ArrayList的优势在于动态的增长数组,
非常适合初始时总长度未知的情况下使用。
LinkedList的优势在于在中间位置插入和删除操作,
速度是最快的。
LinkedList实现了List接口,
允许null元素。
此外LinkedList提供额外的
get,remove,insert方法
在LinkedList的首部或尾部。
这些操作使LinkedList可被
用作堆栈(stack),
队列(queue)
或双向队列(deque)。
ArrayList实现了可变大小的数组。
它允许所有元素,
包括null。
每个ArrayList实例都有一个容量(Capacity),
即用于存储元素的数组的大小。
这个容量可随着不断添加新元素而自动增加,
但是增长算法并没有定义。
当需要插入大量元素时,
在插入前可以调用ensureCapacity方法来
增加ArrayList的容量以提高插入效率。
60.什么时候使用ConcurrentHashMap?
快速失败的Java迭代器
可能会引发ConcurrentModifcationException
在底层集合迭代过程中被修改。
故障安全作为发生在实例中的一个副本
迭代是不会抛出任何异常的。
快速失败的故障安全范例定义了
当遭遇故障时系统是如何反应的。
例如,用于失败的快速迭代器ArrayList
和用于故障安全的迭代器ConcurrentHashMap。
ConcurrentHashMap被作为
故障安全迭代器的一个实例,
它允许完整的并发检索和更新。
当有大量的并发更新时,
ConcurrentHashMap此时可以被使用。
这非常类似于Hashtable,
但ConcurrentHashMap不锁定
整个表来提供并发,
所以从这点上ConcurrentHashMap的性能
似乎更好一些。
所以当有大量更新时
ConcurrentHashMap应该被使用。