Hash,Tree数据结构时间复杂度分析:HashMap, HashTable,HashSet,TreeMap 的时间复杂度 程序员
总结:
1. ConcurrentHashMap 与HashMap和Hashtable 最大的不一样在于:put和 get 两次Hash到达指定的HashEntry,github
第一次hash到达Segment,第二次到达Segment里面的Entry,而后在遍历entry链表面试
2:HashSet底层采用的是HashMap进行实现的,可是没有key-value,只有HashMap的key set的视图,HashSet不允许重复的对象算法
3:Hashtable是基于Dictionary类的,而HashMap是基于Map接口的一个实现数组
4:Hashtable里默认的方法是同步的,而HashMap则是非同步的,所以Hashtable是多线程安全的缓存
5:HashMap能够将空值做为一个表的条目的key或者value,HashMap中因为键不能重复,所以只有一条记录的Key能够是空值,安全
而value能够有多个为空,但HashTable不容许null值(键与值均不行)
6:内存初始大小不一样,HashTable初始大小是11,而HashMap初始大小是16,concurrentHashMap的链表超过8个数据以后自动变成红黑树
7:内存扩容时采起的方式也不一样,Hashtable采用的是2*old+1,而HashMap是2*old。
①HashMap的工做原理
HashMap实现了Map接口,Map接口对键值对进行映射。Map中不容许重复的键。Map接口有两个基本的实现,HashMap和TreeMap。TreeMap保存了对象的排列次序,而HashMap则不能。HashMap容许键和值为null。HashMap是非synchronized的,但collection框架提供方法能保证HashMap synchronized,这样多个线程同时访问HashMap时,能保证只有一个线程更改Map。
public Object put(Object Key,Object value) 方法用来将元素添加到map中。
HashMap基于hashing原理,咱们经过put()和get()方法储存和获取对象。当咱们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,而后找到bucket位置来储存值对象。当获取对象时,经过键对象的equals()方法找到正确的键值对,而后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap在每一个链表节点中储存键值对对象。当两个不一样的键对象的hashcode相同时会发生什么? 它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。
一、HashMap和Hashtable的区别
HashMap 支持key=null 可是 Hashtable 不支持 key=null ,concurrentHashMap 也不能够为空
HashTable是Java中的遗留类,如今不怎么用了。也许HashTable类的设计者当时认为null做为key和value是没有什么用的。
HashMap是以后的版本引进的类,它的接口Map表达的意义更为普遍,也许HashMap的设计者认为null做为key和value是有实际意义的,因此才容许为null。
固然实际项目中,真的是有value为null的状况的。key为null的状况比较少见,但不表明没有。HashMap容许null为key和value应当是类的设计者思考让这个类更有用的设计吧
HashMap和HashTable都实现了Map接口,但决定用哪个以前先要弄清楚它们之间的分别。 主要的区别有:线程安全性,同步(synchronization),以及速度
一、Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。
- HashMap几乎能够等价于Hashtable,除了HashMap是非synchronized的,并能够接受null(HashMap能够接受为null的键值(key)和值(value),而Hashtable则不行)。
- HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程能够共享一个Hashtable;而若是没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
- 另外一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。因此当有其它线程改变了HashMap的结构(增长或者移除元素),将会抛出ConcurrentModificationException,但迭代器自己的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并非一个必定发生的行为,要看JVM。这条一样也是Enumeration和Iterator的区别。
- 注意 fail-fast迭代器和enumerator迭代器的区别致使了hashmap和hashtable 遍历的不一样 参考:Iterator和Enumeration的区别
- 因为Hashtable是线程安全的也是synchronized,因此在单线程环境下它比HashMap要慢。若是你不须要同步,只须要单一线程,那么使用HashMap性能要好过Hashtable。
- HashMap不能保证随着时间的推移Map中的元素次序是不变的。
要注意的一些重要术语:
1) sychronized意味着在一次仅有一个线程可以更改Hashtable。就是说任何线程要更新Hashtable时要首先得到同步锁,其它线程要等到同步锁被释放以后才能再次得到同步锁更新Hashtable。
2) Fail-safe和iterator迭代器相关。若是某个集合对象建立了Iterator或者ListIterator,而后其它的线程试图“结构上”更改集合对象,将会抛出ConcurrentModificationException异常。但其它线程能够经过set()方法更改集合对象是容许的,由于这并无从“结构上”更改集合。可是假如已经从结构上进行了更改,再调用set()方法,将会抛出IllegalArgumentException异常。
3) 结构上的更改指的是删除或者插入一个元素,这样会影响到map的结构。
咱们可否让HashMap同步?
HashMap能够经过下面的语句进行同步:
Map m = Collections.synchronizeMap(hashMap);
仅在你须要彻底的线程安全的时候使用Hashtable,而若是你使用Java 5或以上的话,请使用ConcurrentHashMap吧。
HashMap和HashSet都是collection框架的一部分,它们让咱们可以使用对象的集合。collection框架有本身的接口和实现,主要分为Set接口,List接口和Queue接口。Set的集合里不容许对象有重复的值,List容许有重复,它对集合中的对象进行索引,Queue的工做原理是FCFS算法(First Come, First Serve)。
二、HashMap和HashSet的区别
什么是HashSet 底层是HashMap实现的,看源码:
public HashSet() { map = new HashMap<>(); }
HashSet实现了Set接口,它不容许集合中有重复的值,当咱们提到HashSet时,第一件事情就是在将对象存储在HashSet以前,要先确保对象重写equals()和hashCode()方法,这样才能比较对象的值是否相等,以确保set中没有储存相等的对象。若是咱们没有重写这两个方法,将会使用这个方法的默认实现。
public boolean add(Object o)方法用来在Set中添加元素,当元素值重复时则会当即返回false,若是成功添加的话会返回true。
HashSet 就是HashMap的key列,HashSet 能够为空,HashMap的key和Value均可觉得空,TreeMap 不容许为空,由于treeMap须要排序
*HashMap* | *HashSet* |
HashMap实现了Map接口 | HashSet实现了Set接口 |
HashMap储存键值对 | HashSet仅仅存储对象 |
使用put()方法将元素放入map中 | 使用add()方法将元素放入set中 |
HashMap中使用键对象来计算hashcode值 | HashSet使用成员对象来计算hashcode值,对于两个对象来讲hashcode可能相同, 因此equals()方法用来判断对象的相等性,若是两个对象不一样的话,那么返回false |
HashMap比较快,由于是使用惟一的键来获取对象 | HashSet较HashMap来讲比较慢 |
“你用过HashMap吗?” “什么是HashMap?你为何用到它?”
HashMap能够接受null键值和值,而Hashtable则不能;HashMap是非synchronized;HashMap很快;以及HashMap储存的是键值对等等。这显示出你已经用过HashMap,并且对它至关的熟悉。
“你知道HashMap的工做原理吗?” “你知道HashMap的get()方法的工做原理吗?”
你也许会回答“我没有详查标准的Java API,你能够看看Java源代码或者Open JDK。”“我能够用Google找到答案。”
但一些面试者可能能够给出答案,“HashMap是基于hashing的原理,咱们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当咱们给put()方法传递键和值时,咱们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。”这里关键点在于指出,HashMap是在bucket中储存键对象和值对象,做为Map.Entry。这一点有助于理解获取对象的逻辑。若是你没有意识到这一点,或者错误的认为仅仅只在bucket中存储值的话,你将不会回答如何从HashMap中获取对象的逻辑。这个答案至关的正确,也显示出面试者确实知道hashing以及HashMap的工做原理。可是这仅仅是故事的开始,当面试官加入一些Java程序员天天要碰到的实际场景的时候,错误的答案频现。下个问题多是关于HashMap中的碰撞探测(collision detection)以及碰撞的解决方法:
“当两个对象的hashcode相同会发生什么?” 从这里开始,真正的困惑开始了,一些面试者会回答由于hashcode相同,因此两个对象是相等的,HashMap将会抛出异常,或者不会存储它们。而后面试官可能会提醒他们有equals()和hashCode()两个方法,并告诉他们两个对象就算hashcode相同,可是它们可能并不相等。一些面试者可能就此放弃,而另一些还能继续挺进,他们回答“由于hashcode相同,因此它们的bucket位置相同,‘碰撞’会发生。由于HashMap使用链表存储对象,这个Entry(包含有键值对的Map.Entry对象)会存储在链表中。”这个答案很是的合理,虽然有不少种处理碰撞的方法,这种方法是最简单的,也正是HashMap的处理方法。但故事尚未完结,面试官会继续问:
“若是两个键的hashcode相同,你如何获取值对象?” 面试者会回答:当咱们调用get()方法,HashMap会使用键对象的hashcode找到bucket位置,而后获取值对象。面试官提醒他若是有两个值对象储存在同一个bucket,他给出答案:将会遍历链表直到找到值对象。面试官会问由于你并无值对象去比较,你是如何肯定肯定找到值对象的?除非面试者直到HashMap在链表中存储的是键值对,不然他们不可能回答出这一题。
其中一些记得这个重要知识点的面试者会说,找到bucket位置以后,会调用keys.equals()方法去找到链表中正确的节点,最终找到要找的值对象。完美的答案!
许多状况下,面试者会在这个环节中出错,由于他们混淆了hashCode()和equals()方法。由于在此以前hashCode()屡屡出现,而equals()方法仅仅在获取值对象的时候才出现。一些优秀的开发者会指出使用不可变的、声明做final的对象,而且采用合适的equals()和hashCode()方法的话,将会减小碰撞的发生,提升效率。不可变性使得可以缓存不一样键的hashcode,这将提升整个获取对象的速度,使用String,Interger这样的wrapper类做为键是很是好的选择。
若是你认为到这里已经完结了,那么听到下面这个问题的时候,你会大吃一惊。“若是HashMap的大小超过了负载因子(load factor)定义的容量,怎么办?”除非你真正知道HashMap的工做原理,不然你将回答不出这道题。默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)同样,将会建立原来HashMap大小的两倍的bucket数组,来从新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫做rehashing,由于它调用hash方法找到新的bucket位置。
若是你可以回答这道问题,下面的问题来了:“你了解从新调整HashMap大小存在什么问题吗?”你可能回答不上来,这时面试官会提醒你当多线程的状况下,可能产生条件竞争(race condition)。
当从新调整HashMap大小的时候,确实存在条件竞争,由于若是两个线程都发现HashMap须要从新调整大小了,它们会同时试着调整大小。在调整大小的过程当中,存储在链表中的元素的次序会反过来,由于移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了不尾部遍历(tail traversing)。若是条件竞争发生了,那么就死循环了。这个时候,你能够质问面试官,为何这么奇怪,要在多线程的环境下使用HashMap呢?:)
热心的读者贡献了更多的关于HashMap的问题:
- 为何String, Interger这样的wrapper类适合做为键? String, Interger这样的wrapper类做为HashMap的键是再适合不过了,并且String最为经常使用。由于String是不可变的,也是final的,并且已经重写了equals()和hashCode()方法了。其余的wrapper类也有这个特色。不可变性是必要的,由于为了要计算hashCode(),就要防止键值改变,若是键值在放入时和获取时返回不一样的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其余的优势如线程安全。若是你能够仅仅经过将某个field声明成final就能保证hashCode是不变的,那么请这么作吧。由于获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是很是重要的。若是两个不相等的对象返回不一样的hashcode的话,那么碰撞的概率就会小些,这样就能提升HashMap的性能。
- 咱们可使用自定义的对象做为键吗? 这是前一个问题的延伸。固然你可能使用任何对象做为键,只要它遵照了equals()和hashCode()方法的定义规则,而且当对象插入到Map中以后将不会再改变了。若是这个自定义对象时不可变的,那么它已经知足了做为键的条件,由于当它建立以后就已经不能改变了。
- 咱们可使用CocurrentHashMap来代替Hashtable吗?这是另一个很热门的面试题,由于ConcurrentHashMap愈来愈多人用了。咱们知道Hashtable是synchronized的,可是ConcurrentHashMap同步性能更好,由于它仅仅根据同步级别对map的一部分进行上锁。ConcurrentHashMap固然能够代替HashTable,可是HashTable提供更强的线程安全性。看看这篇博客查看Hashtable和ConcurrentHashMap的区别。
我我的很喜欢这个问题,由于这个问题的深度和广度,也不直接的涉及到不一样的概念。让咱们再来看看这些问题设计哪些知识点:
hashing的概念
HashMap中解决碰撞的方法
equals()和hashCode()的应用,以及它们在HashMap中的重要性
不可变对象的好处
HashMap多线程的条件竞争
从新调整HashMap的大小
2、HashMap与HashTable的区别:
一、 继承和实现区别
Hashtable是基于陈旧的Dictionary类,完成了Map接口;HashMap是Java 1.2引进的Map接口的一个实现(HashMap继承于AbstractMap,AbstractMap完成了Map接口)。
Hashtable: 源码:
public class Hashtable<K,V> extends Dictionary<K,V> implements Map<K,V>, Cloneable, java.io.Serializable { .... }
HashMap 源码:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable { .... }
另外:HashSet 继承于AbstractSet
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { ..... }
二、 线程安全不一样
HashTable的方法是同步的,HashMap是未同步,因此在多线程场合要手动同步HashMap。
三、 对null的处理不一样
HashTable不容许null值(key和value都不能够),HashMap容许null值(key和value均可以)。concurrentHashMap 也不能够为空 ,即 HashTable不容许null值其实在编译期不会有任何的不同,会照样执行,只是在运行期的时候Hashtable中设置的话回出现空指针异常。 HashMap容许null值是指能够有一个或多个键所对应的值为null。当get()方法返回null值时,便可以表示 HashMap中没有该键,也能够表示该键所对应的值为null。
所以,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。
四、 方法不一样
HashTable有一个contains(Object value),功能和containsValue(Object value)功能同样。
五、HashTable使用Enumeration,HashMap使用Iterator。(网友看看:我很奇怪, 我用Iterator也能够遍历HashTable啊,参照:HashTable的五种遍历方式
)
六、HashTable中hash数组默认大小是11,增长的方式是 old*2+1。HashMap中hash数组的默认大小是16,并且必定是2的指数。
七、哈希值的使用不一样,HashTable直接使用对象的hashCode,代码是这样的:
int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length;
而HashMap从新计算hash值,并且用与代替求模:
int hash = hash(k); int i = indexFor(hash, table.length); static int hash(Object x) { int h = x.hashCode(); h += ~(h << 9); h ^= (h >>> 14); h += (h << 4); h ^= (h >>> 10); return h; } static int indexFor(int h, int length) { return h & (length-1); }
区别 |
Hashtable |
Hashmap |
继承、实现 |
Hashtable extends Dictionary implements Map, Cloneable,Serializable |
HashMap extends AbstractMap implements Map, Cloneable,Serializable |
线程同步 |
已经同步过的能够安全使用 |
未同步的,可使用Colletcions进行同步MapCollections.synchronizedMap(Mapm) |
对null的处理
|
Hashtable table = new Hashtable(); table.put(null, "Null"); table.put("Null", null); table.contains(null); table.containsKey(null); table.containsValue(null); 后面的5句话在编译的时候不会有异常,可在运行的时候会报空指针异常具体缘由能够查看源代码 public synchronized V put(K key, V value) { // Make sure the value is not null if (value == null) { throw new NullPointerException(); } |
HashMap map = new HashMap(); map.put("Null", null); map.containsKey(null); map.containsValue(null); 以上这5条语句不管在编译期,仍是在运行期都是没有错误的. 在HashMap中,null能够做为键,这样的键只有一个;能够有一个或多个键所对应的值为null。当get()方法返回null值时,便可以表示 HashMap中没有该键,也能够表示该键所对应的值为null。所以,在HashMap中不能由get()方法来判断HashMap中是否存在某个键,而应该用containsKey()方法来判断。 |
增加率 |
protected void rehash() { int oldCapacity = table.length; Entry[] oldMap = table; int newCapacity = oldCapacity * 2 + 1; Entry[] newMap = new Entry[newCapacity]; modCount++; threshold = (int)(newCapacity * loadFactor); table = newMap; for (int i = oldCapacity ; i-- > 0 ;) { for (Entryold = oldMap[i] ; old != null ; ) { Entrye = old; old = old.next; int index = (e.hash & 0x7FFFFFFF) % newCapacity; e.next = newMap[index]; newMap[index] = e; }}} |
void addEntry(int hash, K key, V value, int bucketIndex) { Entrye = table[bucketIndex]; table[bucketIndex] = new Entry(hash, key, value, e); if (size++ >= threshold) resize(2 * table.length); }
|
哈希值的使用 |
HashTable直接使用对象的hashCode,代码是这样的: public synchronizedbooleancontainsKey(Object key) { Entry tab[] = table; int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entrye = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return true; }} return false;} |
HashMap从新计算hash值,并且用与代替求模 public boolean containsKey(Object key) { Object k = maskNull(key); int hash = hash(k.hashCode()); int i = indexFor(hash, table.length); Entry e = table[i]; while (e != null) { if (e.hash == hash && eq(k, e.key)) return true; e = e.next; } return false; } |
8:哈希值的计算方法不一样,Hashtable直接使用的是对象的hashCode,而HashMap则是在对象的hashCode的基础上还进行了一些变化
源代码分析:
private transient HashMap<E,Object> map; private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<E,Object>(); }
从上面的代码中得出的结论是HashSet的确是采用HashMap来实现的,并且每个键都关键同一个Object类的对象,所以键所关联的值没有意义,真正有意义的是键。而HashMap里的键是不容许重复的,
3、HashMap和TreeMap区别
一、HashMap是基于散列表实现的,时间复杂度平均能达到O(1)。
TreeMap基于红黑树(一种自平衡二叉查找树)实现的,时间复杂度平均能达到O(log n)。
二、HashMap、TreeMap都继承AbstractMap抽象类;TreeMap实现SortedMap接口,因此TreeMap是有序的!HashMap是无序的。 接口层次:
public interface SortedMap<K,V> extends Map<K,V> public interface NavigableMap<K,V> extends SortedMap<K,V> public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
三、两种常规Map性能
HashMap:适用于在Map中插入、删除和定位元素。
Treemap:适用于按天然顺序或自定义顺序遍历键(key)。
4.总结:HashMap一般比TreeMap快一点(树和哈希表的数据结构使然),建议多使用HashMap,在须要排序的Map时候才用TreeMap。
3、HashSet原理
HashSet实现Set接口,由哈希表(其实是一个value永远为null的HashMap实例)支持。它不保证set 的迭代顺序;特别是它不保证该顺序恒久不变。此类容许使用null元素。
底层是HashMap,HashSet底层是采用HashMap实现的。HashSet 的实现比较简单,HashSet 的绝大部分方法都是经过调用 HashMap 的方法来实现的,所以 HashSet 和 HashMap 两个集合在实现本质上是相同的。
TeeSet是经过TreeMap实现的,只不过Set用的只是Map的key Map的key和Set都有一个共同的特性就是集合的惟一性.TreeMap更是多了一个排序的功能. hashCode和equal()是HashMap用的,由于无需排序因此只须要关注定位和惟一性便可.
a.hashCode是用来计算hash值的,hash值是用来肯定hash表索引的.
b.hash表
- Map的key和Set都有一个共同的特性就是集合的惟一性.TreeMap更是多了一个排序的功能.
- hashCode和equal()是HashMap用的, 由于无需排序因此只须要关注定位和惟一性便可.
a. hashCode是用来计算hash值的,hash值是用来肯定hash表索引的.
b. hash表中的一个索引处存放的是一张链表, 因此还要经过equal方法循环比较链上的每个对象才能够真正定位到键值对应的Entry.
c. put时,若是hash表中没定位到,就在链表前加一个Entry,若是定位到了,则更换Entry中的value,并返回旧value - 因为TreeMap须要排序,因此须要一个Comparator为键值进行大小比较.固然也是用Comparator定位的.
a. Comparator能够在建立TreeMap时指定
b. 若是建立时没有肯定,那么就会使用key.compareTo()方法,这就要求key必须实现Comparable接口.
c. TreeMap是使用Tree数据结构实现的,因此使用compare接口就能够完成定位了.
Set的实现类的集合对象中不可以有重复元素,HashSet也同样他是使用了一种标识来肯定元素的不重复,HashSet用一种算法来保证HashSet中的元素是不重复的, HashSet采用哈希算法,底层用数组存储数据。默认初始化容量16,加载因子0.75
Object类中的hashCode()的方法是全部子类都会继承这个方法,这个方法会用Hash算法算出一个Hash(哈希)码值返回,HashSet会用Hash码值去和数组长度取模, 模(这个模就是对象要存放在数组中的位置)相同时才会判断数组中的元素和要加入的对象的内容是否相同,若是不一样才会添加进去。
Hash算法是一种散列算法。
Set hs=new HashSet(); hs.add(o); | o.hashCode(); | o%当前总容量 (0--15) | | 不发生冲突 是否发生冲突-----------------直接存放 | | 发生冲突 | 假(不相等) o1.equals(o2)-------------------找一个空位添加 | | 是(相等) 不添加
覆盖hashCode()方法的原则:
一、必定要让那些咱们认为相同的对象返回相同的hashCode值
二、尽可能让那些咱们认为不一样的对象返回不一样的hashCode值,不然,就会增长冲突的几率。
三、尽可能的让hashCode值散列开(两值用异或运算可以使结果的范围更广)
HashSet 的实现比较简单,相关HashSet的操做,基本上都是直接调用底层HashMap的相关方法来完成,咱们应该为保存到HashSet中的对象覆盖hashCode()和equals(),由于再将对象加入到HashSet中时,会首先调用hashCode方法计算出对象的hash值,接着根据此hash值调用HashMap中的hash方法,获得的值& (length-1)获得该对象在hashMap的transient Entry[] table中的保存位置的索引,接着找到数组中该索引位置保存的对象,并调用equals方法比较这两个对象是否相等,若是相等则不添加,注意:因此要存入HashSet的集合对象中的自定义类必须覆盖hashCode(),equals()两个方法,才能保证集合中元素不重复。在覆盖equals()和hashCode()方法时, 要使相同对象的hashCode()方法返回相同值,覆盖equals()方法再判断其内容。为了保证效率,因此在覆盖hashCode()方法时, 也要尽可能使不一样对象尽可能返回不一样的Hash码值。
若是数组中的元素和要加入的对象的hashCode()返回了相同的Hash值(相同对象),才会用equals()方法来判断两个对象的内容是否相同。
public class HashSet<E> extends AbstractSet<E> implements Set<E>, Cloneable, java.io.Serializable { static final long serialVersionUID = -5024744406713321676L; private transient HashMap<E,Object> map; private static final Object PRESENT = new Object(); public HashSet() { map = new HashMap<>(); } public HashSet(Collection<? extends E> c) { map = new HashMap<>(Math.max((int) (c.size()/.75f) + 1, 16)); addAll(c); } public boolean add(E e) { return map.put(e, PRESENT)==null; } public boolean remove(Object o) { return map.remove(o)==PRESENT; } ....... }
4、ConcurrentMap
ConcurrentHashMap 表现区别:不能够有null键,线程安全,原子操做。一个ConcurrentHashMap 由多个segment 组成,每一个segment 包含一个Entity 的数组。这里比HashMap 多了一个segment 类。该类继承了ReentrantLock 类,因此自己是一个锁。当多线程对ConcurrentHashMap 操做时,不是彻底锁住map, 而是锁住相应的segment 。这样提升了并发效率。缺点:当遍历ConcurrentMap中的元素时,须要获取全部的segment 的锁,使用遍历时慢。锁的增多,占用了系统的资源。使得对整个集合进行操做的一些方法
5、LinkedHashMap是HashMap的一个子类
LinkedHashMap保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先获得的记录确定是先插入的.也能够在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种状况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,由于LinkedHashMap的遍历速度只和实际数据有关,和容量无关,而HashMap的遍历速度和他的容量有关。参考: ConcurrentHashMap原理分析
6、java为数据结构中的映射定义了一个接口java.util.Map;它有四个实现类,分别是HashMap Hashtable LinkedHashMap 和TreeMap.
由于TreeSet 不能为空,可是HashSet能够为空,由于TreeSet须要排序,HashSet不须要,空的没法排序