Java集合容器面试题
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几种实现。
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的区别?
线程安全: 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以前的版本。
不同版本JDK的HashMap的实现的区别以及原因
(1)链表元素的插入方式不一样
JDK1.7用的是头插法,而JDK1.8及之后使用的都是尾插法
因为JDK1.7是用单链表进行的纵向延伸,当采用头插法时会容易出现逆序且环形链表死循环问题。
在JDK1.8之后是因为加入了红黑树使用尾插法,能够避免出现逆序且链表死循环的问题。
(2)扩容后数据存储位置的计算方式不一样
在JDK1.7的时候是直接用hash值和需要扩容的二进制数进行&(这里就是为什么扩容的时候为啥一定必须是2的多少次幂的原因所在,因为如果只有2的n次幂的情况时最后一位二进制数才一定是1,这样能最大程度减少hash碰撞)(hash值 & length-1)
在JDK1.8的时候直接用了JDK1.7的时候计算的规律,也就是扩容前的原始位置+扩容的大小值=JDK1.8的计算方式,而不再是JDK1.7的那种异或的方法。但是这种方式就相当于只需要判断Hash值的新增参与运算的位是0还是1就直接迅速计算出了扩容后的储存方式。
(3)数据结构不一样
JDK1.7的时候使用的是数组+ 单链表的数据结构。
在JDK1.8及之后时,使用的是数组+链表+红黑树的数据结构(当链表的深度达到8的时候,也就是默认阈值,就会自动扩容把链表转成红黑树的数据结构来把时间复杂度从O(n)变成O(logN)提高了效率)
(4)为什么在JDK1.8中HashMap把链表转化为红黑树的阈值是8?
由于treenodes的大小大约是常规节点的两倍,因此我们仅在容器包含足够的节点以保证使用时才使用它们,当它们变得太小(由于移除或调整大小)时,它们会被转换回普通的node节点,容器中节点分布在hash桶中的频率遵循泊松分布,桶的长度超过8的概率非常非常小。所以作者应该是根据概率统计而选择了8作为阀值(Java注释中的解释)
Hashmap的结构,1.7和1.8有哪些区别
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和ConcurrentSkipListSet是TreeMap和TreeSet的有序容器的并发版本
ConcurrentSkipListMap的底层是通过跳表来实现的。跳表(Skiplist)是一个链表,但是通过使用“跳跃式”查找的方式使得插入、读取数据时复杂度变成了O(logn),跳表以空间换时间,是基于链表实现的一种类似“二分”的算法。
CopyOnWriteArrayList使用了一种叫写时复制的方法,当有新元素添加到CopyOnWriteArrayList时,先从原有的数组中拷贝一份出来,然后在新的数组做写操作,写完之后,再将原来的数组引用指向到新数组,合适读多写少的场景
ConcurrentSkipListMap与CopyOnWriteArrayList
Hashmap 什么时候进行扩容呢?
默认大小为16、负载因子为0.75,即超过12就扩容,容量扩大一倍
新建hashmap时设置初始大小,假如有1000个元素,不能设置1000,因为元素数量为750时就会自动扩容,要避免自动扩容,要让元素数量不超过初始容量的0.75
扩容时会重新计算元素在数组中的位置,尽量避免扩容
Hash的公式—> index = HashCode(Key) & (Length - 1)
因为resize的赋值方式,也就是使用了单链表的头插入方式,同一位置上新元素总会被放在链表的头部位置,在旧数组中同一条Entry链上的元素,通过重新计算索引位置后,有可能被放到了新数组的不同位置上。会形成环形列表。
使用头插会改变链表的上的顺序,但是如果使用尾插,在扩容时会保持链表元素原本的顺序,就不会出现链表成环的问题了。Java8在同样的前提下并不会引起死循环,原因是扩容转移后前后链表顺序不变,保持之前节点的引用关系。
HashMap的默认初始化长度是多少?为什么?
默认是16,可以是其他2的幂
hash函数可以将任意长度的输入经过变化以后得到固定长度的输出,如果两个元素不相同,但是hash函数的值相同,这两个元素就是一个碰撞
为了减少hash值的碰撞,需要实现一个尽量均匀分布的hash函数,在HashMap中index = key的hashcode值 & length-1
长度16或者其他2的幂时,length - 1的值是所有二进制位全为1,这种情况下index的结果等同于hashcode后几位的值,只要输入的hashcode本身分布均匀,hash算法的结果就是均匀的
所以HashMap的默认长度为16,是为了降低hash碰撞的几率
哈希表如何解决Hash冲突?
Java集合容器面试题的更多相关文章
- Java集合容器简介
Java集合容器主要有以下几类: 1,内置容器:数组 2,list容器:Vetor,Stack,ArrayList,LinkedList, CopyOnWriteArrayList(1.5),Attr ...
- [转载]Java集合容器简介
Java集合容器主要有以下几类: 1,内置容器:数组 2,list容器:Vetor,Stack,ArrayList,LinkedList, CopyOnWriteArrayList(1.5),Attr ...
- 面霸篇:Java 集合容器大满贯(卷二)
面霸篇,从面试角度作为切入点提升大家的 Java 内功,所谓根基不牢,地动山摇. 码哥在 <Redis 系列>的开篇 Redis 为什么这么快中说过:学习一个技术,通常只接触了零散的技术点 ...
- 50道Java集合经典面试题(收藏版)
前言 来了来了,50道Java集合面试题也来啦~ 已经上传github: https://github.com/whx123/JavaHome 1. Arraylist与LinkedList区别 可以 ...
- Java——(二)Java集合容器
------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 一.基本概念 1)Collection:一个独立元素的序列,这些元素都服从一条或多条规则.Lis ...
- 【面试题】Java集合部分面试题
集合与数组? 数组:(可以存储基本数据类型)是用来存储对象的一种容器,但是数组的长度固定,不适合在对象数量未知的情况下使用 集合:(只能存储对象,对象类型可以不一样)集合的长度可变,可在多数情况下使用 ...
- 编程体系结构(03):Java集合容器
本文源码:GitHub·点这里 || GitEE·点这里 一.集合容器简介 集合容器是Java开发中最基础API模块,通常用来存储运行时动态创建的元素,基本特点如下: 泛型特点,存储任意类型对象: 动 ...
- java 集合 + 常见面试题
1.1. 集合概述 1.1.1. Java 集合概览 从下图可以看出,在 Java 中除了以 Map 结尾的类之外, 其他类都实现了 Collection 接口. 并且,以 Map 结尾的类都实现了 ...
- Java集合容器的深度理解
Java容器里有很多写好的容器API,这使我们很方便的可以存储.操作我们的数据. 下面是我写的容器的特点,一些容器的不同之处,从底层源码解析一下容器实现原理 一.常用的容器目录 上图可以看出,java ...
- Java集合框架面试题
www.cnblogs.com/zhxxcq/archive/2012/03/11/2389611.html 这里的两个图很形象,由于放进图片链接,图片显示不了,所以只能给出该链接. Java集合框架 ...
随机推荐
- debug指南-基础bug
基础Bug 本章节主要阐述一些最基本的bug. 虽然这些bug看起来很弱智简单,但正是因为这些小bug,让我们调代码的时间增加至少 \(2,3\) 个小时. 本系列的宗旨就是综合这些小bug极其对应解 ...
- glibc 内存分配与释放机制详解
作者:来自 vivo 互联网存储团队- Wang Yuzhi 本文以一次线上故障为基础介绍了使用 glibc 进行内存管理可能碰到问题,进而对库中内存分配与释放机制进行分析,最后提供了相应问题的解决方 ...
- RMI原理及常见反序列化攻击手法
这是对网上一些文章和视频的再总结,可以参考以下资料,师傅们分析的都挺详细了,我这就是记录一下师傅们写的博客. 廖雪峰 - 给了简单的小例子,了解即可 B站视频(白师傅) 先知社区(小阳师傅) - 讲的 ...
- Windows系统下PhpStorm+Xdebug安装与调试
环境说明: 系统:Windows10 PhpStorm:2019.3.2 PHP版本:7.3.21 Xdebug版本 :2.7.2 一.Xdebug介绍 官网地址:https://xdebug.org ...
- Redis集群搭建-Docker
使用docker搭建本地集群Redis服务(参考:https://www.cnblogs.com/slowbirdoflsh/p/11633113.html) 环境配置 # 拉取redis镜像 doc ...
- SharpZipLib打.tar.gz压缩包
.tar.gz是linux上常见的压缩格式,linux默认支持这种压缩格式,所以我们经常见到在linux系统上的.tar.gz包. 这里我们说一下怎么用SharpZipLib来打一个.tar.gz的压 ...
- Javascript 常用封装(二)
1.字符串占位宽度 计算占位宽度:字符串的占位宽度除了涉及到具体的字符串内容,还与字体大小有关,可以将其放入Dom中来获取实际占位宽度 //计算字符串的占位宽度 function getTextWid ...
- GObject学习笔记(二)类型创建与注册
前言 本文可在https://paw5zx.github.io/GObject-tutorial-beginner-02/中阅读,体验更加 在上一节中我们介绍了GObject类型的类和实例变量的创建和 ...
- k8s 实战 3----副本集
副本集是什么?我们在前文中讲过什么是pod,简单来说pod就是k8s直接操作的基本单位.不了解的同学可以参考前文: k8s 实战 1 ---- 初识 (https://www.cnblogs.com/ ...
- Java 动态设置 JVM 参数的方法
Java虚拟机(JVM)在运行Java应用时,其性能调优和资源管理至关重要.虽然许多JVM参数在启动时通过命令行设置,但在应用运行期间动态调整某些参数也是可行的.通过动态设置JVM参数,开发者可以更有 ...