Java集合
- 常见的集合问题
- HashMap与HashTable的区别
- HashMap的put方法的具体流程?
- HashMap 解决哈希冲突
- HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
- HashMap在JDK1.7和JDK1.8中有哪些不同?
- 为什么HashMap中String、Integer这样的包装类适合作为K?
- ConcurrentHashMap和Hashtable的区别?
- Java集合的快速失败机制 “fail-fast”
- ArrayList 和 Vector 的区别?
- ArrayList和LinkedList的区别?
- Array 和 ArrayList 有什么区别?什么时候该应 Array 而不是 ArrayList 呢?
- HashSet是如何保证数据不可重复的?
- BlockingQueue是什么?
转自 https://zhuanlan.zhihu.com/p/82714518
常见的集合问题
- Map 接口和 Collection 接口是所有集合框架的父接口:
- Collection 接口的子接口包括:Set接口和List接口
- Map接口的实现类主要有:HashMap、TreeMap、Hashtable、ConcurrentHashMap以及Properties等
- Set接口的实现类主要有:HashSet、TreeSet、LinkedHashSet等
- List接口的实现类主要有:ArrayList、LinkedList、Stack以及Vector等
HashMap与HashTable的区别
- HashMap没有考虑同步,是线程不安全的;Hashtable使用了synchronized关键字,是线程安全的;
- HashMap允许Key/Value都为null;后者Key/Value都不允许为null;
- HashMap继承自AbstractMap类;而Hashtable继承自Dictionary类;
HashMap的put方法的具体流程?
HashMap 解决哈希冲突
什么是哈希?
Hash,一般翻译为“散列”,也有直接音译为“哈希”的,这就是把任意长度的输入通过散列算法,变换成固定长度的输出,该输出就是散列值(哈希值);这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来唯一的确定输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。
所有散列函数都有如下一个基本特性:根据同一散列函数计算出的散列值如果不同,那么输入值肯定也不同。但是,根据同一散列函数计算出的散列值如果相同,输入值不一定相同。
什么是哈希冲突?
当两个不同的输入值,根据同一散列函数计算出相同的散列值的现象,我们就把它叫做碰撞(哈希碰撞)。
所以结论是:
- 使用链地址法(使用散列表)来链接拥有相同hash值的数据;
- 使用2次扰动函数(hash函数)来降低哈希冲突的概率,使得数据分布更平均;
- 引入红黑树进一步降低遍历的时间复杂度,使得遍历更快;
HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?
hashCode()方法返回的是int整数类型,其范围为-(2 ^ 31)~(2 ^ 31 - 1),约有40亿个映射空间,而HashMap的容量范围是在16(初始化默认值)~2 ^ 30,HashMap通常情况下是取不到最大值的,并且设备上也难以提供这么多的存储空间,从而导致通过**hashCode()**计算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置;
HashMap在JDK1.7和JDK1.8中有哪些不同?
JDK 1.7,JDK 1.8存储结构数组 + 链表数组 + 链表 + 红黑树初始化方式单独函数:inflateTable()直接集成到了扩容函数resize()中hash值计算方式扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算存放数据的规则无冲突时,存放数组;冲突时,存放链表无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树插入数据方式头插法(先讲原位置的数据移到后1位,再插入数据到该位置)尾插法(直接插入到链表尾部/红黑树)扩容后存储位置的计算方式全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1))按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量)
为什么HashMap中String、Integer这样的包装类适合作为K?
- String、Integer等包装类的特性能够保证Hash值的不可更改性和计算准确性,能够有效的减少Hash碰撞的几率
- 都是final类型,即不可变性,保证key的不可更改性,不会存在获取hash值不同的情况
- 内部已重写了equals()、hashCode()等方法,遵守了HashMap内部的规范(不清楚可以去上面看看putValue的过程),不容易出现Hash值计算错误的情况;
ConcurrentHashMap和Hashtable的区别?
ConcurrentHashMap 结合了 HashMap 和 HashTable 二者的优势。HashMap 没有考虑同步,HashTable 考虑了同步的问题。但是 HashTable 在每次同步执行时都要锁住整个结构。ConcurrentHashMap 锁的方式是稍微细粒度的。
Java集合的快速失败机制 “fail-fast”
java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。
例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。
原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。
解决办法:
-
在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。
-
使用CopyOnWriteArrayList来替换ArrayList
ArrayList 和 Vector 的区别?
这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存储在这两个集合中的元素位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引来取出某个元素,并且其中的数据是允许重复的,这是与 HashSet 之类的集合的最大不同处,HashSet 之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素。
ArrayList 与 Vector 的区别主要包括两个方面:
同步性:
Vector 是线程安全的,也就是说它的方法之间是线程同步(加了synchronized 关键字)的,而 ArrayList 是线程不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用 ArrayList,因为它不考虑线程安全的问题,所以效率会高一些;如果有多个线程会访问到集合,那最好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。
数据增长:
ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个人超过了容量时,就需要增加 ArrayList 和 Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要去的一定的平衡。Vector 在数据满时(加载因子1)增长为原来的两倍(扩容增量:原容量的 2 倍),而 ArrayList 在数据量达到容量的一半时(加载因子 0.5)增长为原容量的 (0.5 倍 + 1) 个空间。
ArrayList和LinkedList的区别?
- LinkedList 实现了 List 和 Deque 接口,一般称为双向链表;ArrayList 实现了 List 接口,动态数组;
- LinkedList 在插入和删除数据时效率更高,ArrayList 在查找某个 index 的数据时效率更高;
- LinkedList 比 ArrayList 需要更多的内存;
Array 和 ArrayList 有什么区别?什么时候该应 Array 而不是 ArrayList 呢?
- Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
- Array 大小是固定的,ArrayList 的大小是动态变化的。
- ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。
- 对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
HashSet是如何保证数据不可重复的?
HashSet的底层其实就是HashMap,只不过我们HashSet是实现了Set接口并且把数据作为K值,而Value值一直使用一个相同的虚值来保存
BlockingQueue是什么?
Java.util.concurrent.BlockingQueue是一个队列,在进行检索或移除一个元素的时候,它会等待队列变为非空;当在添加一个元素时,它会等待队列中的可用空间。BlockingQueue接口是Java集合框架的一部分,主要用于实现生产者-消费者模式。我们不需要担心等待生产者有可用的空间,或消费者有可用的对象,因为它都在BlockingQueue的实现类中被处理了。Java提供了集中BlockingQueue的实现,比如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue,、SynchronousQueue等。