java中HashMap学习

来源:互联网 发布:网络系统集成课程设计 编辑:程序博客网 时间:2024/05/22 04:54

什么是HashMap
HashMap基于hashing原理,我们通过put()和get()方法储存和获取对象。当我们将键值对传递给put()方法时,它调用键对象的hashCode()方法来计算hashcode,让后找到bucket位置来储存值对象。当获取对象时,通过键对象的equals()方法找到正确的键值对,然后返回值对象。HashMap使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。

public V put(K key, V value) { if (key == null)  return putForNullKey(value); int hash = hash(key.hashCode()); int i = indexFor(hash, table.length);  // bucket位置 for (Entry<K,V> e = table[i]; e != null; e = e.next) {  Object k;  if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {   V oldValue = e.value;   e.value = value;   e.recordAccess(this);   return oldValue; // 如果该Key已经存在,会返回之前保存的值  } } modCount++; addEntry(hash, key, value, i); return null;}public V get(Object key) { if (key == null)  return getForNullKey(); int hash = hash(key.hashCode()); for (Entry<K,V> e = table[indexFor(hash, table.length)];   e != null;   e = e.next) {  Object k;  if (e.hash == hash && ((k = e.key) == key || key.equals(k)))   return e.value; } return null;}

 

 

当两个对象的hashcode相同会发生什么?
它们会储存在同一个bucket位置的链表中。键对象的equals()方法用来找到键值对。

如果HashMap的大小超过了负载因子定义容量,怎么办?
默认的负载因子大小为0.75,也就是说,当一个map填满了75%的bucket时候,和其它集合类(如ArrayList等)一样,将会创建原来HashMap大小的两倍的bucket数组,来重新调整map的大小,并将原来的对象放入新的bucket数组中。这个过程叫作rehashing,因为它调用hash方法找到新的bucket位置。

addEntry(hash, key, value, i);void addEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<K,V>(hash, key, value, e); if (size++ >= threshold)  resize(2 * table.length);}void resize(int newCapacity) { Entry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) {  threshold = Integer.MAX_VALUE;  return; } Entry[] newTable = new Entry[newCapacity]; transfer(newTable); table = newTable; threshold = (int)(newCapacity * loadFactor);}

 
如果使用的是默认,即直接new HashMap<String, Object>(),那么在put第12个元素时,就会进行resize操作

static final int DEFAULT_INITIAL_CAPACITY = 16;static final float DEFAULT_LOAD_FACTOR = 0.75f;public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR); table = new Entry[DEFAULT_INITIAL_CAPACITY]; init();}

 
所以你应该评估自己可能缓存的数量,使用HashMap(int initialCapacity, float loadFactor)构造一个带指定初始容量和加载因子的空 HashMap。

public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0)  throw new IllegalArgumentException("Illegal initial capacity: " +             initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY)  initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor))  throw new IllegalArgumentException("Illegal load factor: " +             loadFactor); // Find a power of 2 >= initialCapacity int capacity = 1; while (capacity < initialCapacity)  capacity <<= 1; this.loadFactor = loadFactor; threshold = (int)(capacity * loadFactor); table = new Entry[capacity]; init();}

 

重新调整HashMap大小存在什么问题
当重新调整HashMap大小的时候,存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。
上面看到resize会操作table,而操作方法本身和table都是非线程安全

 

为什么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中之后将不会再改变了。如果这个自定义对象时不可变的,那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了。

 

请您到ITEYE网站看 java小强 原创,谢谢!

http://cuisuqiang.iteye.com/ 

自建博客地址:http://www.javacui.com/ ,内容与ITEYE同步!

0 0
原创粉丝点击