用Hashmap实现redis有什么问题
Redis的缓存可以持久化,Map是内存对象,程序一重启数据就没了;Redis可以实现分布式的缓存,Map只能存在创建它的程序里;Redis缓存有过期机制,Map本身无此功能;Redis可以用几十G内存来做缓存,Map不行,一般JVM也就分几个G数据就够大了;hashmap不是线程安全的,可以考虑concurrentHashmap
遍历Hashmap的三种方式
for (int key : hashmap.keySet()); while (Iterator.hasNext()); for(Map.Entry<Integer, String> entry : hashmap.entrySet()); for (String value : hashmap.values())
Hashmap如果只有一个写其他全读会出什么问题
单线程写多线程读时,get操作可能得到null值:写线程如果触发了扩容,扩容过程中会先新建临时空数组再赋给成员数组(真正放KeyValue的),此时成员数组没有任何元素,再循环根据元素的hash放入空的成员数组的对应的桶位置,过程中度线程获取元素可能为null;
多线程写多线程读时,可能会导致get操作时死循环(JDK1.8已解决)、put非null元素后get操作得到null值、元素丢失3个问题,都是多线程写触发扩容resize,重写分配元素桶和链表上的位置导致的;
JDK1.8之前并发操作Hashmap时为什么会有死循环的问题?
JDK1.7采用数组+链表实现,put元素时若元素个数超过阈值会触发扩容,扩容是原数组的2倍,执行resize方法,resize会调用transfer方法转移老数组中的元素,转移后的链表是倒序的(如果在新表的数组索引位置相同时),多线程转移过程中设置e.next很容易形成环,get操作时就形成死循环;
JDK1.8采用数组+链表+红黑树实现,元素是在链表末端进行添加,链表顺序不会变,因此不会形成环,就没有死循环的问题,尽管如此HashMap任是不安全的;
代替HashMap:Hashtable,ConcurrentHashMap,Collections.synchronizedMap;
Hashmap内部的数据结构是什么?底层是怎么实现的?
JDK7的HashMap采用数组+链表实现,JDK8采用数组+链表+红黑树实现,数组定义Node<K, V>[] table,当链表节点大于8转换为红黑树,小于6转换为链表,链表是为了解决哈希冲突,红黑树是为了提高性能,线程不安全;
扩容:默认16,增长因子0.75,超过容量(设置的容量会转换成2n次方)*增长因子时将进行扩容,容量翻倍,HashMap要求容量必须是2的幂,resize方法循环将老元素放入新容器;
Hashmap如何解决hash冲突,为什么Hashmap中的链表需要转成红黑树?
采用拉链法解决,put元素时根据key的hashcode再hash得到key的hashcode,根据hashcode和数组长度计算得到元素存储的位置即桶的位置,如果桶为空则放到桶上,如果非空说明存在hashcode相同的元素即hash冲突,则把该元素放到链表末尾;
单链表的查询复杂度是O(n),红黑树查询复杂度是O(logn),为了提高查询效率,节点数为8时会将链表转成红黑树,为6时转成链表;选择8是由概率统计决定的,理想情况下好的hash算法可以尽可能避免hash冲突,节点分布在桶中的频率遵循泊松分布,链表中元素个数为8时概率很小,避免链表和树的频繁转换;
Hashmap什么时候会触发扩容?
当HashMap中的元素越来越多的时候,碰撞的几率也就越来越高(因为数组的长度是固定的),所以为了提高查询的效率,就要对HashMap的数组进行扩容;
当元素个数超过数组大小*0.75时就会扩容,扩容大小为原大小*2,然后重新计算每个元素所在的位置,这是扩容过程中最耗时的操作;所以已知大小时初始化时设置容量可提高性能,需要注意设置的容量和扩容因子的关系,如1000个元素,最好设置为靠近1000/0.75的2次幂2048;
Hashmap扩容时每个entry需要再计算一次hash吗?
JDK7会,JDK8不会; 由于扩容是原来的2倍即2次幂,所以元素的位置要么在原位置,要么在原位置再移动2次幂的位置,JDK8中通过使用e.hash & oldCap来计算高位和低位的hash值,来把原来在一个槽位上面的链表拆分成两个链表即可;JDK1.7中rehash的时候,旧链表迁移新链表的时候,如果在新表的数组索引位置相同,则链表元素会倒置,JDK1.8不会倒置;
Hashmap的数组长度为什么要保证是2的幂?
用&运算代替%运算,若不为2的幂次,内部数组会存在浪费 ;扩容时方便定位,当相与的该位为0时,则结果不变,扩容后索引值不变,当相与的该位为1时,则结果为原索引值加原数组长度 ;
Hashmap key重复了怎么办? 是如何解决的?
put元素时先计算key的hashcode得到桶的位置,再equals查找有无相等的元素,有则直接更新,没有则放入链表;即hashcode和equals都相等才认为key相等,IdentityHashMap只要==即地址相等才认为key相同,从而可以实现放入重复的key;
Hashmap有什么漏洞会导致他变慢?
其中一个问题:在一个Key被放进HashMap和从 HashMap取出时两次计算它的hashCode不一致(中间对计算hashcode的成员变量做了修改)就会导致获取不到元素,可能导致内存泄露;另外如果Key的hashcode计算不正确,导致所有元素的hashcode落到一个桶中,导致桶很大,查询就会变慢;
如何给Hashmap的key对象设计他的hashcode?
实现hashcode时需重写equals;hashCode的结果具有一致性,即无论何时计算得到的结果都一样,这就要求计算hashCode依赖的值是不可变的;hashCode必须基于对象的内容生成;hashCode产生的散列码最好能均匀分布;
用过哪些Map类,都有什么区别,Hashmap是线程安全的吗,并发下使用的Map是什么,他们内部原理分别是什么?
HashMap:底层实现:JDK7的HashMap采用数组+链表实现,JDK8采用数组+链表+红黑树实现,当链表节点大于8转换为红黑树,小于6转换为链表,链表是为了解决哈希冲突,红黑树是为了提高性能,线程不安全;扩容:默认16,增长因子0.75,超过容量(设置的容量会转换成2n次方)*增长因子时将进行扩容,容量翻倍,HashMap要求容量必须是2的幂,resize方法循环将老元素放入新容器;允许空元素;LinkedHashMap:继承HashMap,LinkedHashMap=HashMap+双向链表维护插入或访问顺序;TreeMap:基于红黑树实现,有序(基于key和Comparable排序),遍历速度较快,线程不安全;不允许空元素;Hashtable:方法有synchronized,线程安全;IdentityHashMap:Key可重复,只要对象地址不相等;WeakHashMap:同HashMap,Entry继承弱引用,每次resize等情况会清理引用;
JAVA8的ConcurrentHashmap为什么放弃了分段锁,有什么问题吗,如果你来设计,你如何设计。
JDK1.7中的Segment继承了ReentrantLock,每个锁控制的是一段,当每个Segment越来越大时,锁的粒度就变得有些大了,而且Segment越多越耗内存;JDK1.8放弃了分段锁而是用了Node锁,只需要对头结点使用synchronized进行同步,减低锁的粒度,提高性能,并使用CAS操作来确保Node的一些操作的原子性;
使用synchronized而不用ReentrantLock,一是为了减少内存开销,如果使用ReentrantLock则需要节点继承AQS来获得同步支持,增加内存开销,而1.8中只有头节点需要进行同步;二是内部优化,synchronized则是JVM直接支持的,JVM能够在运行时作出相应的优化措施:锁粗化、锁消除、锁自旋等等
Concurrenhashmap求size是如何加锁的,如果刚求完一段后这段发生了变化该如何处理
JDK1.7中,size操作通过一种比较巧的方式避免对所有的segment加锁,Segment中的有一个modCount变量,代表的是对Segment中元素的数量造成影响的操作的次数,这个值只增不减,size操作就是遍历了两次Segment,每次记录Segment的modCount值,然后将两次的modCount进行比较,如果相同,则表示期间没有发生过写入操作,就将原先遍历的结果返回,如果不相同,则把这个过程再重复做一次,如果再不相同,则就需要将所有的Segment都锁住,然后一个一个遍历了;
JDK1.8中,size操作比较简单,没有加锁,而是在插入删除数据时维护volatile long baseCount(元素个数)和volatile CounterCell[] counterCells(并发时更新baseCount失败的元素个数),然后累加即可;
如何用LinkedHashmap实现LRU?
LRU最近最少使用,实现思路是维持一个链表,把刚被插入的节点放在链表尾,刚被访问的节点脱链,也放到链表尾,每次需要淘汰节点的时候就淘汰链表头;LinkedHashMap底层实现同HashMap,增加了双向链表来记录节点的插入或者访问顺序,同时节点按桶分布避免了遍历链表时的全表扫描,构造函数时设置按访问顺序链接节点,这样链表头就是最久没访问的节点,同时构造时重写removeEldestEntry方法(默认返回false,需根据缓存大小和链表长度判断,当链表长度超过缓存大小时返回true)来删除头结点,同时需对LinkedHashMap的方法使用synchronized包装保证线程安全;
LinkedHashmap基本原理
LinkedHashMap继承HashMap,增加了head/tail指向头尾Entry节点的指针,LinkedHashMap=HashMap+双向链表,通过双向链表维护节点的顺序,支持按插入(默认)或者访问顺序排序;LinkedHashMap中的节点元素为Entry<K,V>直接继承HashMap.Node<K,V>,增加了before/after指向前后Entry节点的指针;
ArrayList与Vector区别
Vector是ArrayList的线程安全版本,ArrayList和Vector绝大部分方法都是相同的,只是Vector的方法增加了synchronized修饰;ArrayList使用transient Object[] elementData,实现了writeObject、readObject方法定制序列化,Vector没有transient,只实现了writeObject方法,没有完全定制,从序列化机制的角度来看,ArrayList的实现比Vector的实现更安全; 扩容时,ArrayList扩充50%,Vector扩充一倍,会有更多的空间浪费;
ArrayList与LinkedList的实现和区别
ArrayList是基于动态数组实现,LinkedList基于双向链表实现;对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针;对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动其后的所有数据;
TreeMap:了解数据结构、了解其key对象为什么必须要实现Compare接口、如何用它实现一致性哈希
TreeMap基于红黑树(自平衡二叉排序树)实现,要求Key要能自然排序(实现了Comparable接口)或者构造时传入Comparator,由于是红黑树所以添加删除时需要维护红黑树的结构,containsKey/get/put/remove的时间复杂度是log(n),实现了NavigableMap接口,所以有一系列的导航方法;
TreeMap基于红黑树实现,可用来实现一致性hash算法,原因是可以存储有序的数据、查询效率较高O(logn)、提供了一些高效的导航的方法可以直接使用如tailMap方法可以查找比n大的集合、增加虚拟节点比较方便; 实现代码
Java中的HashSet内部是如何工作的
HashSet内部采用HashMap来实现,构造函数时内部会new HashMap实现,由于Map需要key和value,所以HashSet中所有key的都有一个默认value,其他方法都是调用Map的方法实现;
网友评论