HashMap、HashTable、ConcurrentHashMap、HashSet区别 线程安全类

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的一个实现。

  1. HashMap几乎能够等价于Hashtable,除了HashMap是非synchronized的,并能够接受null(HashMap能够接受为null的键值(key)和值(value),而Hashtable则不行)。
  2. HashMap是非synchronized,而Hashtable是synchronized,这意味着Hashtable是线程安全的,多个线程能够共享一个Hashtable;而若是没有正确的同步的话,多个线程是不能共享HashMap的。Java 5提供了ConcurrentHashMap,它是HashTable的替代,比HashTable的扩展性更好。
  3. 另外一个区别是HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。因此当有其它线程改变了HashMap的结构(增长或者移除元素),将会抛出ConcurrentModificationException,但迭代器自己的remove()方法移除元素则不会抛出ConcurrentModificationException异常。但这并非一个必定发生的行为,要看JVM。这条一样也是Enumeration和Iterator的区别。
  4. 注意 fail-fast迭代器和enumerator迭代器的区别致使了hashmap和hashtable 遍历的不一样 参考:Iterator和Enumeration的区别
  5. 因为Hashtable是线程安全的也是synchronized,因此在单线程环境下它比HashMap要慢。若是你不须要同步,只须要单一线程,那么使用HashMap性能要好过Hashtable。
  6. 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的问题:

  1. 为何String, Interger这样的wrapper类适合做为键? String, Interger这样的wrapper类做为HashMap的键是再适合不过了,并且String最为经常使用。由于String是不可变的,也是final的,并且已经重写了equals()和hashCode()方法了。其余的wrapper类也有这个特色。不可变性是必要的,由于为了要计算hashCode(),就要防止键值改变,若是键值在放入时和获取时返回不一样的hashcode的话,那么就不能从HashMap中找到你想要的对象。不可变性还有其余的优势如线程安全。若是你能够仅仅经过将某个field声明成final就能保证hashCode是不变的,那么请这么作吧。由于获取对象的时候要用到equals()和hashCode()方法,那么键对象正确的重写这两个方法是很是重要的。若是两个不相等的对象返回不一样的hashcode的话,那么碰撞的概率就会小些,这样就能提升HashMap的性能。
  2. 咱们可使用自定义的对象做为键吗? 这是前一个问题的延伸。固然你可能使用任何对象做为键,只要它遵照了equals()和hashCode()方法的定义规则,而且当对象插入到Map中以后将不会再改变了。若是这个自定义对象时不可变的,那么它已经知足了做为键的条件,由于当它建立以后就已经不能改变了。
  3. 咱们可使用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.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不须要,空的没法排序

参考:HashMap底层实现原理/HashMap与HashTable区别/HashMap与HashSet区别

相关文章
相关标签/搜索