扫码关注公众号

java语言类和对象之map、set集合
03-09
176观看
01

HashMap 为什么线程不安全?

JDK7存在死循环和数据丢失问题。数据丢失:1、并发赋值被覆盖:在createEntry方法中,新添加的元素直接放在头部,使元素之后可以被更

来自:容器和Map-Map、set集合(后序会删除)
02

Java都有哪些map,分别怎么实现的,具体讲(阿里面试题)

map的主要特点是键值对的形式,一一对应,且一个key只对应1个value,且key唯一。其常用的map实现类主要有HashMap、HashTable、TreeMap、ConcurrentHashMap、LinkedHashMap、weakHashMap等等。1、HashMap使用哈希表实现(最近的jdk1.8改用红黑树存储而非哈希表),它是线程不安全的Map,方法上都没有synchronized关键字修饰,具体可以参考https://blog.csdn.net/qq_30683329/article/details/804545182、HashTablehashTable是线程安全的一个map实现类,它实现线程安全的方法是在各个方法上添加了synchronized关键字。但是现在已经不再推荐使用HashTable了,因为现在有了ConcurrentHashMap这个专门用于多线程场景下的map实现类,其大大优化了多线程下的性能。3、ConcurrentHashMap这个map实现类是在jdk1.5中加入的,其在jdk1.6/1.7中的主要实现原理是segment段锁,它不再使用和HashTable一样的synchronized一样的关键字对整个方法进行加锁,而是转而利用segment段落锁来对其进行加锁,以保证Map的多线程安全。其实可以理解为,一个ConcurrentHashMap是由多个HashTable组成,所以它允许获取不用段锁的线程同时持有该资源,segment有多少个,理论上就可以同时有多少个线程来持有它这个资源。其默认的segment是一个数组,默认长度为16。也就是说理论上可以提高16倍的性能。但是要注意咯,在JAVA的jdk1.8中则对ConcurrentHashMap又再次进行了大的修改,取消了segment段锁字段,采用了CAS+Synchronized技术来保障线程安全。底层采用数组+链表+红黑树的存储结构,也就是和HashMap一样。这里注意Node其实就是保存一个键值对的最基本的对象。其中Value和next都是使用的volatile关键字进行了修饰,以确保线程安全。4、TreeMapTreeMap也是一个很常用的map实现类,因为他具有一个很大的特点就是会对Key进行排序,使用了TreeMap存储键值对,再使用iterator进行输出时,会发现其默认采用key由小到大的顺序输出键值对,如果想要按照其他的方式来排序,需要重写也就是override它的compartor接口。此处引用一下其他大神的代码:1importjava.util.Comparator;2importjava.util.Iterator;3importjava.util.Set;4importjava.util.TreeMap;567publicclassCompare{8publicstaticvoidmain(String[]args){9TreeMap<String,Integer>map=newTreeMap<String,Integer>(newxbComparator());10map.put("key_1",1);11map.put("key_2",2);12map.put("key_3",3);13Set<String>keys=map.keySet();14Iterator<String>iter=keys.iterator();15while(iter.hasNext())16{17Stringkey=iter.next();18System.out.println(""+key+":"+map.get(key));19}20}21}22classxbComparatorimplementsComparator23{24publicintcompare(Objecto1,Objecto2)25{26Stringi1=(String)o1;27Stringi2=(String)o2;28return-i1.compareTo(i2);29}30}另外,TreeMap底层的存储结构也是一颗红黑树。是不是发现好多都是红黑树,没错因为红黑树查找效率高,只有O(lgn)。它是一种自平衡的二叉查找树。在每次插入和删除节点时,都可以自动调节树结构,以保证树的高度是lgn。5、LinkedHashMapLinkedHashMap它的特点主要在于linked,带有这个字眼的就表示底层用的是链表来进行的存储。相对于其他的无序的map实现类,还有像TreeMap这样的排序类,linkedHashMap最大的特点在于有序,但是它的有序主要体现在先进先出FIFIO上。没错,LinkedHashMap主要依靠双向链表和hash表来实现的。仔细看,这里虽然在计算hashcode时还是发生了hash冲突,采用了链地址法解决了冲突,但是这里的Entry对象是采用双向链表保存的,每个Entry都有一个after和before的属性。当插入一个entry时,如果发生了冲突,就可以将新的Entry插入Entry链表中的头部,但是按照双向链表的角度来说,又会将该Entry插入到双向链表的尾部。6、weakHashMap首先,weakHashMap它是一个“弱键”,它的Key值和Value都可以是null,而且其Map中如果这个Key值指向的对象没被使用,此时触发了GC,该对象就会被回收掉的。其原理主要是使用的WeakReference和ReferenceQueue实现的,其key就是weakReference,而ReferenceQueue中保存了被回收的Key-Value。如果当其中一个Key-Value不再使用被回收时,就将其加入ReferenceQueue队列中。当下次再次调用该WeakHashMap时,就会去更新该map,比如ReferenceQueue中的key-value,将其中包含的key-value全部删除掉。这就是所谓的“自动删除”。

来自:容器和Map-Map、set集合(后序会删除)
03

HashMap树化条件?退化条件?

树化条件:HashMap具体实现类中有两个变量staticfinalintTREEIFY_THRESHOLD=8;staticfinalintMIN_TREEIFY_CAPACITY=64;哈希表的链表树化成红黑树有两个条件: 链表长度大于TREEIFY_THRESHOLD 哈希数组的长度大于MIN_TREEIFY_CAPACITY退化条件:扩容resize()时,红黑树拆分成的lo和hi两颗红黑树,每一颗树的结点数小于等于临界值6时退化成链表。移除元素remove()时,在removeTreeNode()方***检查红黑树是否满足退化条件,与结点数无关。如果红黑树根root为空,或者root的左子树/右子树为空,root.left.left根的左子树的左子树为空,都会发生红黑树退化成链表。

来自:容器和Map-Map、set集合(后序会删除)
04

HashMap和HashTable的区别

1.两者父类不同HashMap是继承自AbstractMap类,Hashtable是继承自Dictionary类。不过它们都实现了同时实现了map、Cloneable(可复制)、Serializable(可序列化)这三个接口。2.对外提供的接口不同Hashtable比HashMap多提供了elments()和contains()两个方法。elments()方法继承自ashtable的父类Dictionnary。elements()方法用于返回此Hashtable中的value的枚举。contains()方法判断该Hashtable是否包含传入的value。它的作用与containsValue()一致。事实上,contansValue()就只是调用了一下contains()方法。3.对null的支持不同Hashtable:key和value都不能为null。HashMap:key可以为null,但是这样的key只能有一个,因为必须保证key的唯一性;可以有多个key值对应的value为null。4.安全性不同HashMap是线程不安全的,在多线程并发的环境下,可能会产生死锁等问题,因此需要开发人员自己处理多线程的安全问题。Hashtable是线程安全的,它的每个方法上都有synchronized关键字,因此可直接用于多线程中。虽然HashMap是线程不安全的,但是它的效率远远高于Hashtable,这样设计是合理的,因为大部分的使用场景都是单线程。当需要多线程操作的时候可以使用线程安全的ConcurrentHashMap。ConcurrentHashMap虽然也是线程安全的,但是它的效率比Hashtable要高好多倍。因为ConcurrentHashMap使用了分段锁,并不对整个数据进行锁定。5.初始容量大小和每次扩充容量大小不同6.计算hash值的方法不同

来自:容器和Map-Map、set集合(后序会删除)
05

jdk7和jdk8的hashmap有什么区别

DK7是数组+链表,JDK8是数组+链表/红黑树。1、链表插入方式的不同在1.7之前,链表元素的插入采用的是头插法,也就是说,当有新结点进来时,会在插入在链表的头部。很明显,由于不用遍历链表,这种插入方式的效率是更高的。但是1.8之后,因为当结点插入的时候,本身就要为了判断元素的个数而遍历链表(看看是否达到了树化的阈值),所以就可以搭一个顺风车,在遍历完之后,把结点插入到链表尾部,即采用的尾插法。这种方式也解决了多线程下可能引发的死锁问题。因为头插法的链表在扩容移动时,会被逆序,即后插入的先被处理,如果这个时候有另一线程进行get操作,就有可能引发死锁2、插入时机不同1.7之前是扩容后再插入新的数据,并且不会先计算插入值的哈希值,最后单独算。1.8之后是先插入再扩容,插入的值和大家一起计算新的哈希值。

来自:容器和Map-Map、set集合(后序会删除)
课程
专栏
三大集合:List、Map、Set的区别与联系
List和Set是存储单列数据的集合,Map是存储键值对这样的双列数据的集合。
csdn
map
set集合
【校招VIP】java的Map集合 详解Map集合
csdn
map
【校招VIP】java中的Set集合
csdn
set
java语言-容器和Map-Map、set集合(后序会删除)
3专栏
1课程
5 试题