Java面试题 集合七题 day.1

1.hashmap和hashtable的区别

1.线程方面: HashMap时非线程安全的,HashTable是线程安全的;HashTable内部的方法基本都经过synchronized修饰.如果需要多线程的情况可以使用concurrentHashMap,在多线程情况下效率比HashTable更好
2.效率方面 : HashTable基本上所有方法都加上了锁,所以效率非常差,HashTable基本被淘汰不使用
3.null值 : 在HashMap中,null可以作为Key,而且可以有多个Value为null,在HashTable中,只要有一个为空就会抛出空指针异常
4.初始容量以及扩容: 在HashMap中,①如果不指定初始值的话,在put进第一个元素的时候会默认初始化大小为16,并且在每次扩容的时候都会扩容成原来的两倍②如果指定了初始值,HashMap会调用tableSizeFor将传入的初始化大小转化成大于参数的最接近2次幂.
在HashTable,如果不指定初始值的话,Hashtable初始化大小为11,每次扩容容量都会变成原来的2n+1
5.底层数据结构: HashMap在jdk1.8之后引入了红黑树,在拉链法接的链表长度大于8并且元素总数大于64的时候就会转化成红黑树结构.在链表长度小于6的时候有退化成红黑树,HashTable中没有这样的机制

2.hashmap底层实现

在jdk1.8之前是采用数组加链表实现,HashMap中的每一个节点的hash值都是通过key的hashCode通过扰动函数的道德,然后通过(n-1)&hash判断元素存放位置,n是hashmap的数组长度,如果当前位置存在元素,就判断存入的节点hash值与key值是否相等,如果相等就直接覆盖,如果不相等就采用拉链法解决冲突
拉链法就是,如果产生了Hash冲突,就将当前元素连接到冲突元素后面(采用的尾插法)
扰动函数可以减少hash冲突的次数

在jdk1.8之后引入了数组加链表加红黑树结构,TreeMap,TreeSet引入了红黑树结构,主要目的是为了提高查找效率,hash冲突次数过多,链表长度过长,可能会导致hashmap查找效率从O(1)降到O(n),所以对长度大于8的链表将其转化成红黑树.提高查找效率

3.HashMap为什么加载因子是0.75

提高空间利用率和 减少查询成本的折中,主要是泊松分布,0.75的话碰撞最小,

HashMap有两个参数影响其性能:初始容量和加载因子。容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动扩容之前可以达到多满的一种度量。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行扩容、rehash操作(即重建内部数据结构),扩容后的哈希表将具有两倍的原容量。

通常,加载因子需要在时间和空间成本上寻求一种折衷。

加载因子过高,例如为1,虽然减少了空间开销,提高了空间利用率,但同时也增加了查询时间成本;

加载因子过低,例如0.5,虽然可以减少查询时间成本,但是空间利用率很低,同时提高了rehash操作的次数。

在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少rehash操作次数,所以,一般在使用HashMap时建议根据预估值设置初始容量,减少扩容操作。

4.为什么Map底层使用红黑树实现

红黑树是二叉查找树,但在每个节点增加一个存储为表示节点的颜色,可以是红色或黑色(非红即黑),通过对任意一条从根到叶子的路径上各个节点着色方式的限制,红黑树确保没有一条路径会比其他路径长两倍。因此,它是一种弱平衡二叉树,相对于严格的AVL树来说,它的旋转次数少,所以对于查找、插入、删除较多的情况下,通常使用红黑树。

红黑树与AVL比较:

  1. AVL是严格平衡的,频繁的插入和删除,会引起频繁的rebalance,导致效率降低;红黑树是”近似平衡“的。,算是一种折中,插入最多旋转2次,删除最多旋转3次。所以红黑树在查找、插入删除的复杂度都是O(logn),且性能稳定.
  2. 红黑树的高度只比高度平衡的AVL树的高度(log2n)仅仅大了一倍,在性能上却好很多。

5.HashMap的扩容操作是怎么实现的?

1.判断当前容量大小是否为空,如果为空,则把容量扩容为16
2.获取key的hashcode ,对hashcode进行扰动处理,计算除元素下标
3.根据下标判断有无hash碰撞,如果没有就直接放到hashmap的数组中
4.如果发生碰撞,比较两个key是否相同,相同则覆盖,不相同则以链表的方式插入到尾部
5.如果插入后链表长度超过了阈值(Treelfy_Threshold = 8),则把链表转化为红黑树
6.插入成功后,如果元素个数达到了阈值(size = 容量 * 阈值),则执行扩容操作判断(容量最大值是1<<31)
7.扩容成功之后,对元素的下标进行重新计算

6.ConcurrentHashMap底层实现?实现原理?

1.jdk1.7

  • 首先加数据分为一段一段的存储,然后给每一段配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问
  • 在jdk1.7中,ConcurrentHashMap采用segment + hashEntry 的方式实现的,segment是一个数组,segment的每一个节点面都包含hashentry数组,当hashentry数组被修改的时候,必须先获得对应segment的锁.

2.jdk1.8

  • jdk1.8中放弃了segment的设计,二十采用了Node + CAS + Synchronized 来保证并发安全实现,sync只锁住当前链表或者红黑树的首节点,只要hash不冲突就不会产生并发问题

7.fail-fast机制

是java集合的一种错误检测机制,当多个线程对集合惊醒结构上的改变操作的时候,有可能会产生fail-fast机制.
比如线程1在遍历list集合,在这个时候线程二删除了list集合一个元素,那么这个时候就会抛出ConcurrentModficationException

原因是因为,collection里都有一个modcount的值,每当迭代器在使用hasnext遍历下一个元素的时候都会检测modcount值是否有变化,如果有变化就终止遍历抛出异常,没有则继续遍历

解决方法:
1.在操作过程中,所有世纪到改变modcount值的地方都加上sync
2.使用CopyOnwriteArrayList来替换ArrayList

7.说说CopyOnWriteArrayList

CopyOnWrite容器即写时复制的容器。通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素后,再将原容器的引用指向新的容器。这样做可以使CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器
CopyOnWriteArrayList的特性
1.线程安全的,多线程环境下可以直接使用,无需加锁;
2.通过锁 + 数组拷贝 + volatile 关键字保证了线程安全;
3.每次数组操作,都会把数组拷贝一份出来,在新数组上进行操作,操作成功之后再赋值回去。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值