主题 | 链接 |
---|---|
Java基础知识 | 面试题 |
Java集合框架 | 面试题 |
Java并发编程 | 面试题 |
Redis | 面试题 |
文章目录
- 常用集合类的使用
- HashMap与HashTable的区别?
- JDK1.8以后HashMap的put方法的具体流程?
- ArrayList、LinkList、Vetor的区别
- HashMap、HashTable、ConcurrenHashMap的区别
- HashMap 和 ConcurrentHashMap 的区别?
- Java8中stream相关用法
- 不同版本JDK的HashMap的实现的区别以及原因
- Collection和Collections的区别
- Arrays.asList获得的List使用时需要注意什么
- fail-fast和fail-safe
- CopyOnWriteArrayList、ConcurrentSkipListMap
- Hashmap 什么时候进行扩容呢?
- HashMap的默认初始化长度是多少?为什么?
- 哈希表如何解决Hash冲突?
- 为什么 HashMap 中 String、Integer 这样的包装类适合作为 key 键
- HashMap 中的 key若为 Object类型, 则需实现哪些方法?
- List、Map、Set 三个接口,存取元素时,各有什么特点?
- Set 里的元素是不能重复的,那么用什么方法来区分重复与否呢? 是用 == 还是equals()? 它们有何区别?
- Java 集合类框架的最佳实践有哪些?
常用集合类的使用
- Collection接口的子接口包括:Set接口和List接口
- Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
- Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
- List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
HashMap与HashTable的区别?
- HashMap没有考虑同步,是线程不安全的;Hashtable使用了synchronized关键字,是线程安全的;
- HashMap允许K/V都为null;后者K/V都不允许为null;
- HashMap继承自AbstractMap类;而Hashtable继承自Dictionary类;
JDK1.8以后HashMap的put方法的具体流程?
当 HashMap 中有大量的元素都存放到同一个桶中时,这个桶下有一条长长的链表,这个时候 HashMap 就相当于一个单链表,假如单链表有 n 个元素,遍历的时间复杂度就是 O(n),完全失去了它的优势。
针对这种情况,JDK 1.8 中引入了 红黑树(查找时间复杂度为 O(logn))来优化这个问题。
ArrayList、LinkList、Vetor的区别
List主要有ArrayList、LinkedList与Vector几种实现。
这三者都实现了List 接口,使用方式也很相似,主要区别在于因为实现方式的不同,所以对不同的操作具有不同的效率。
- ArrayList
是一个可改变大小的数组.当更多的元素加入到ArrayList中时,其大小将会动态地增长.内部的元素可以直接通过get与set方法进行访问,因为ArrayList本质上就是一个数组. - LinkedList
是一个双链表,在添加和删除元素时具有比ArrayList更好的性能.但在get与set方面弱于ArrayList.当然,这些对比都是指数据量很大或者操作很频繁的情况下的对比,如果数据和运算量很小,那么对比将失去意义. - Vector
和ArrayList类似,但属于强同步类。如果你的程序本身是线程安全的(thread-safe,没有在多个线程之间共享同一个集合/对象),那么使用ArrayList是更好的选择。 - Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%.而 LinkedList 还实现了 Queue 接口,该接口比List提供了更多的方法,包offer(),peek(),poll()等. 注意:默认情况下ArrayList的初始容量非常小,所以如果可以预估数据量的话,分配一个较大的初始值属于最佳实践,这样可以减少调整大小的开销。
HashMap、HashTable、ConcurrenHashMap的区别
HashMap和HashTable有何不同?
- 线程安全: HashTable 中的方法是同步的,而HashMap中的方法在默认情况下是非同步的。在多线程并发的环境下,可以直接使用HashTable,但是要使用HashMap的话就要自己增加同步处理了。
- 继承关系: HashTable是基于陈旧的Dictionary类继承来的。
HashMap继承的抽象类AbstractMap实现了Map接口。 - 允不允许null值:HashTable中,key和value都不允许出现null值,否则会抛出NullPointerException异常。HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。
- 默认初始容量和扩容机制: HashTable中的hash数组初始大小是11,增加的方式是old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。
- 哈希值的使用不同 : HashTable直接使用对象的hashCode。 HashMap重新计算hash值。
- 遍历方式的内部实现上不同 : Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。 HashMap 实现Iterator,支持fast-fail,Hashtable的 Iterator 遍历支持fast-fail,用 Enumeration不支持 fast-fail
HashMap 和 ConcurrentHashMap 的区别?
- ConcurrentHashMap和HashMap的实现方式不一样,虽然都是使用桶数组实现的,但是还是有区别,ConcurrentHashMap对桶数组进行了分段,而HashMap并没有。
- ConcurrentHashMap在每一个分段上都用锁进行了保护。HashMap没有锁机制。所以,前者线程安全的,后者不是线程安全的。
PS:以上区别基于jdk1.8以前的版本。
Java8中stream相关用法
Stream不是集合元素,更不是数据结构,它跟数据的存储没有任何关系,它只是一种针对数据的计算而存在的,可以把它看成是更高级的迭代器。
不同版本JDK的HashMap的实现的区别以及原因
Collection和Collections的区别
- Collection:是集合类的上层接口。本身是一个Interface,里面包含了一些集合的基本操作。Collection接口是Set接口和List接口的父接口
- Collections:Collections是一个集合框架的帮助类,里面包含一些对集合的排序,搜索以及序列化的操作。
Arrays.asList获得的List使用时需要注意什么
Arrays.asList得到的List它的长度是不能改变的。当你向这个List添加或删除一个元素时(例如 list.add(“d”);)程序就会抛出异常(java.lang.UnsupportedOperationException)。
public static List asList(T… a) {
return new ArrayList<>(a);
}
当你看到这段代码时可能觉得没啥问题啊,不就是返回了一个ArrayList对象吗?问题就出在这里。这个ArrayList不是java.util包下的,而是java.util.Arrays.ArrayList,显然它是Arrays类自己定义的一个内部类!这个内部类没有实现add()、remove()方法,而是直接使用它的父类AbstractList的相应方法。而AbstractList中的add()和remove()是直接抛出java.lang.UnsupportedOperationException异常的!
fail-fast和fail-safe
- 线程不安全的类,并发情况下可能会出现快速失败;线程安全的类,可能会出现安全失败
- 一个线程在遍历,另一个线程在添加、删除或修改,就会出现并发修改的问题
- 当遍历时检测到并发修改,就会抛出异常:concurrentmodificationException,这就是快速失败
- ArrayList.iterator()返回一个迭代器对象,其中使用一个int类型的expectedModCount记录状态,当发生添加、删除、修改操作时会更改这个值,当遍历时调用next()会检查这个值跟开始遍历时是否一致,发现expectedModCount发生了变化,就意味着有并发修改,这时候就抛出异常iterator.remove()方法没有进行modCount值的检查,并且手动把expectedModCount值修改成了modCount值,这又保证了下一次迭代的正确。
- 而fail-safe是一个概念,并发容器的并发修改不会抛出异常,这和其实现有关。并发容器的iterate方法返回的iterator对象,内部都是保存了该集合对象的一个快照副本,并且没有modCount等数值做检查。这也造成了并发容器的iterator读取的数据是某个时间点的快照版本。你可以并发读取,不会抛出异常,但是不保证你遍历读取的值和当前集合对象的状态是一致的!这就是安全失败的含义。
CopyOnWriteArrayList、ConcurrentSkipListMap
ConcurrentSkipListMap与CopyOnWriteArrayList
Hashmap 什么时候进行扩容呢?
- 默认大小为16、负载因子为0.75,即超过12就扩容,容量扩大一倍
- 新建hashmap时设置初始大小,假如有1000个元素,不能设置1000,因为元素数量为750时就会自动扩容,要避免自动扩容,要让元素数量不超过初始容量的0.75
- 扩容时会重新计算元素在数组中的位置,尽量避免扩容
- Hash的公式—> index = HashCode(Key) & (Length - 1)
- 因为resize的赋值方式,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置,在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。会形成环形列表。
- 使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。
HashMap的默认初始化长度是多少?为什么?
因为在使用2的幂的数字的时候,Length-1的值是所有二进制位全为1,这种情况下,index的结果等同于HashCode后几位的值。
只要输入的HashCode本身分布均匀,Hash算法的结果就是均匀的。
这是为了实现均匀分布。
https://blog.csdn.net/qq_36520235/article/details/82417949