HashMap 的底层原理

本文详细探讨了HashMap的存储结构(数组+链表/红黑树)、构造方法、put操作、扩容策略以及get操作的实现细节,重点比较了JDK1.7和1.8的不同之处,帮助理解HashMap性能优化和并发控制机制。
摘要由CSDN通过智能技术生成

HashMap 的底层原理

前言:

关于java基础,在hashMap底层原理这个问题上可以说是非常经典的问题了;下面就讨论下hashMap的底层原理;

底层源码:
一,JDK1.7 HashMap的底层源码:
1.1 HashMap 的存储结构:
 HashMap的存储结构是数组+链表的结合(在jdk1.8之后,添加了红黑树结构(具体看下面jdk1.8介绍))
 ,当实例化一个HashMap时,系统会建立一个长度为capacity的entry数组,在这个数组中可以存
储元素的位置,我们称为“桶”(bucket),;每个bucket都有自己的索引,系统可以通过索引快速的
查到到bucket中的元素;(底层通过hash算法,把元素存储到对应的索引中的bucket中,如果有多的就存储到
对应的链表中)每个bucket中都带有一个引用变量用来指向下一个元素(链表了);因此,在一个bucket中
就生成了一条entry链;entry是hashMap中的基本组成单元,每个entry包含了一哥key-value键值对;
entry是HashMap中的一个静态内部类(初始化);
static class Entry<K,V> implements Map.Entry<K,V>{
   final K key;
   V value;
   Entry<K,V> next; // 存储指向下一个entry的引用,单链表结构;
   int hash; // 对key的hashcod值进行hash运算的得到的值,存储在entry中,避免重复运算
   /**
   * create  new entry
   */
   Entry(int h,K K,V v,Entry<K,V> n){
     value=v;
     next=n;
     key = k;
     hash=h;
   }
}

而jdk1.8之后为Node(除了给hash值加了final修饰外,表示不可变之外没有什么变化):

static class Node<K,V> implements Map.Entry<K,V>{
   final int hash;
   final K key;
   V value;
   Node<K,V> next;
   Node(int hash,K key,V value,Node<K,V> next){
       this.hash=hash;
       this.key=key;
       this.value=value;
       this.next=next;
   }
}

经过以上分析,hashMap的存储结构可以用下图来表示:
在这里插入图片描述
在这里插入图片描述
元素中hash值的算法为:key的hash值%len(数组/桶 的长度);

1.2 构造方法:
先看下hashMap中的几个重要的属性
//1<<4=2的4次方 默认的初始容量为16;
static final int Default_INITIAL_CAPACITY=1<<4;
// 最大容量为2的30次方
static final int MAXIMUM_CAPACITY=1>>30;
// 默认的装载因子
static final float DEFUALT_LOAD_FACTOR=0.75F;
// HashMap内部的存储结构为一个数组,此处的数组为空,即没有初始化之前的状态
static final Entry<?,?>[] EMPTY_TABLE={};
// (向下转型)空的存储实体 transient表示不需要序列化的属性
transient Entry<K,V>[] table=(Entry<K,V>)EMPTY_TABLE;
// 实际存储key-value键值对的个数
transient int size;
// **阈值**,当table=={} 值,该值为初始容量(16);当table被填充时,threshold的值为capacity*loadFactor;hashMap在进行扩容时需要参照threshold;
int threshold;
// 负载因子,代表了table的填充度有多少,默认的值为0.75
final float loadFactor;
// 快速失败(由于hashMap是线程非安全性的,所以在hashMap参与迭代的过程中,如果出现了其他线程参与而导致hashMap的结果发生了改变(如,put,remove操作),需要抛出ConcurrentModifactionException异常)
transient int modCount;
// 默认的threshold的值; 
 // Integer类中 @Native public static final int   MAX_VALUE = 0x7fffffff; integer最大取值方位21亿
static final int ALTERNATIVE_HASHING_THRESHOLD_DEFUALT=Integer.MAX_VALUE;
//计算hash值时的key
transient int hashSeed=0;

jdk1.8之后:(jdk1.8之后添加了红黑树,当超过8时,转化为红黑树,当到6时,换为数组+链表的结构;中间7为缓冲)

 private static final long serialVersionUID = 362498820763181265L;
    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     * 当链表的长度为8时,并且数组的长度大于64,才会转化为红黑树,否则只会对数组进行扩容,
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     * 当链表的长度为6时,又转换回来
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     * 最小树形化阈值:当hash表中的容量(数组的长度)>64时,链表才会转化为红黑树
     * 如果桶中的数量过大,不是为树形化(转为红黑树),而是要进行扩容
     * 为了避免扩容,树形化的选择冲突,这个值不能小于4*TREEINF_THRESHOLD
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

    transient Node<K,V>[] table;

    /**
     * Holds cached entrySet(). Note that AbstractMap fields are used
     * for keySet() and values().
     */
    transient Set<Map.Entry<K,V>> entrySet;

    /**
     * The number of key-value mappings contained in this map.
     */
    transient int size;

    transient int modCount;

    int threshold;

    final float loadFactor;

HashMap 有4个构造器(如果构造器没有自带参数,那么会使用默认的16初始容量和0.75的加载因子)

//通过初始容量和状态因子来构造HashMap
public HashMap(int initialCapacity,float loadFactor){
   //参数有效性的检查
   if(initialCapacity<0){
    throw new IllegalArgumentException("Illegal initial Argument" +initialCapacity);
}
 if(initialCapacity>MAXIMUM_CAPACITY) 
  initialCapacity=MAXIMUM_CAPACITY;
  if(loadFactor<=0 || Float.isNaN(loadFactor)){
  threw new IllegalArgumentException("Illegal loadFactory Aragument"+loadFactor);
  }
  this.loadFactory=loadFactory;
  threshold=initialCapacity;
  init(); // 在hashMap中没有具体的实现,不过在其子类linkedHashMap中就会有对应的实现
}
// 通过扩容因子来构造HashMap,容量默认值16
public HashMap(int initialCapacity ){
   this(initialCapacity,Default_LOAD_FACTOR);
}
// 无参构造函数
public HashMap(){
this(DEFAULT_INITIALCAPACITY,DEFAULT_LOAD_FACTORY);
//通过其他Map来初始化HashMap,容量通过Map中的size来计算,转载因子为0.75
public HashMap(Map<? extends K,? extends V> m){
  this(Math.max((int) (m.size()/DEFAULT_LOAD_FACTOR)+1,DEFAULT_INITAL_CAPACITY),DEFAULT_LOAD_FACTOR);
  //初始化HashMap的底层数组结构
  inflateTable(threshold);
  // 添加m中的元素
  putAllForCreate(m);
}
}

jdk1.8中的代码:

   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);
        this.loadFactor = loadFactor;
        this.threshold = tableSizeFor(initialCapacity);
    }
    
// 通过异或最后加1,返回的是2的整数次幂;
   static final int tableSizeFor(int cap) {
        int n = cap - 1;
        n |= n >>> 1;
        n |= n >>> 2;
        n |= n >>> 4;
        n |= n >>> 8;
        n |= n >>> 16;
        return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
    }
 public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }
        public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    }
     public HashMap(Map<? extends K, ? extends V> m) {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        putMapEntries(m, false);
    }
    final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
        int s = m.size();
        if (s > 0) {
            if (table == null) { // pre-size
                float ft = ((float)s / loadFactor) + 1.0F;
                int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                         (int)ft : MAXIMUM_CAPACITY);
                if (t > threshold)
                    threshold = tableSizeFor(t);
            }
            else if (s > threshold)
                resize();
            for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
                K key = e.getKey();
                V value = e.getValue();
                putVal(hash(key), key, value, false, evict);
            }
        }
    }

从上可以看出,在常规的构造器中,并没有马上为table数组分配空间(参数为指定map的构造器除外);其他的都是在第一次进行put操作的时候构建数组table;

1.3put 操作

如果在插入值时,两个key通过hash运算得到相同的值,即发生hash碰撞时,就会把值存储在当前桶的链表上,在jdk1.7是头插法,即把当前值插到头结点之后,把当前插入的值的指针指向以前的第一个值;而在jdk1.8之后,变为了尾插法;(主要数解决了链表成环的问题);

public V put(K key , V vaue){
 //如果table数组为空数组{},进行数组填充(为table分配实际内存空间),入参为threshold,此时 threshold为initialCapacity 默认是1<<4(=16) 
 if (table == EMPTY_TABLE) { inflateTable(threshold);//分配数组空间
  }
  //如果key为null,存储位置为table[0]或table[0]的冲突链上 
  if (key == null) return putForNullKey(value);
   int hash = hash(key);
   //对key的hashcode进一步计算,确保散列均匀 int i = indexFor(hash, table.length);
   //获取在table中的实际位置
for (Entry<K,V> e = table[i]; e != null; e = e.next) {
 //如果该对应数据已存在,执行覆盖操作。用新value替换旧value,并返回旧
 value Object k;
  if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 
  V oldValue = e.value; e.value = value;
   e.recordAccess(this);
   //调用value的回调函数,其实这个函数也为空实现 
   return oldValue; } }
   modCount++;//保证并发访问时,若HashMap内部结构发生变化,快速响应失败 
   addEntry(hash, key, value, i);//新增一个
   entry return null;
   }
}


而构造函数中的参数map中的inflateTable 方法的代码如下:

private void inflateTable(int toSize){
    int  capacity=roundUpToPowerOf2(toSize);//capacity一定是2的整数次幂
    threshold=(int) Math.min(loadFactory*capacity,MAXIMUM_CAPACITY+1);
    // 此处为threshold赋值
    table=new Entry[capacity]; 
    
   // 分配空间
   initHashSeedAsNeeded(capacity);// 选择合适的Hash因子;  
}

private static int roundUpToPowerOf2(int number) {
 // assert number >= 0 : "number must be non-negative"; 
 return number >= MAXIMUM_CAPACITY ? MAXIMUM_CAPACITY : (number > 1) ? Integer.highestOneBit((number - 1) << 1) : 1;
  }

在对数组进行空间分配后,会根据hash函数来计算散列值;

// 用了很多异或,移位等运算,对key的hashcod进行进一步的计算和二进制位的不断调整,来保证最终获取的存储位置尽量的分布均匀
final int hash(Object k){
   int h=hashSeed;
   if(0!=h && k instanceof String){// 这里针对String优化了hash函数,是否使用新的hash函数和hash因子有关;
   return  sun.misc.Hashing.stringHash32((String)k);
}
 h ^= k.hashcode();
 h ^= (h>>>20) ^ (h>>>12);
  retrun h^ (h >>>7) ^ (h>>>4);
}

从上可以看出,影响hashMap元素的存储位置只与key相关,而与value无关,通过hash函数得到的散列值后,在通过indexFor来进一步处理来获取实际的存储位置;

static int indexFor(int h,int length){
   return h &(length-1);
}

h & (length-1), 保证获取的index在一定的数组长度范围内,有的版本对此处的运算会使用取模运算,也能保证了index在一定的数组长度范围内;但是,位运算对于计算机来说 性能更好;(hashMap中有大量的位运算);
所以要得到一个元素的存储位置有一下步骤:
1.获取该元素的key值;
2.通过hash方法得到key的散列值,这其中需要用到key的hashcode值;
3.通过indexFor方法来计算存储位置;(h ^ (length-1))

最后,得到存储位置下标后,我们就可以把元素放入hashMap中;

void addEntry(int hash , K key, V value ,int bucketIndex){
   if ((size >= threshold) && (null != table[bucketIndex])) {
    resize(2 * table.length);//当size超过临界阈值threshold,并且即将发生哈希冲突时进行扩 容,新容量为旧容量的2倍
   hash = (null != key) ? hash(key) : 0;
    bucketIndex = indexFor(hash, table.length);//扩容后重新计算插入的位置下标
}
//把元素放入HashMap的桶的对应位置 
createEntry(hash, key, value, bucketIndex); }

//创建元素 
void createEntry(int hash, K key, V value, int bucketIndex) { 
Entry<K,V> e = table[bucketIndex]; //获取待插入位置元素 
table[bucketIndex] = new Entry<>(hash, key, value, e);//这里执行链接操作,使得新插入的元 素指向原有元素。 
//这保证了新插入的元素总是在链表的头
 size++;//元素个数+1 
 }

jdk1.8:

// 插入,如果插入的key值hashcode值已存在就会替换原来的旧值
 public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }
    
 final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            tab[i] = newNode(hash, key, value, null);
        else {
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                e = p;
            else if (p instanceof TreeNode)
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            treeifyBin(tab, hash);
                        break;
                    }
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

由上可知,当发生hash冲突,并且size大于阈值的时候,需要进行数组的扩容,扩容时,需要新建一个原来数组2倍的新数组,然后将Entry元素中的所有内容都传输过去,所以扩容时相当消耗资源的;

1.4扩容操作:

扩容是通过resize操作来实现的;

//按新的容量扩容Hash表 
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, initHashSeedAsNeeded(newCapacity));//将老的表中的数据拷贝到新的结构中
    table = newTable;//修改HashMap的底层数组
     threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);//修改阀值
      }

//将老的表中的数据拷贝到新的结构中 
void transfer(Entry[] newTable, boolean rehash) { 
int newCapacity = newTable.length;//容量
for (Entry<K,V> e : table) { //遍历所有桶
 while(null != e) {
  //遍历桶中所有元素(是一个链表) 
  Entry<K,V> next = e.next; 
  if (rehash) {//如果是重新Hash,则需要重新计算hash值 
  e.hash = null == e.key ? 0 : hash(e.key); 
  }
  int i = indexFor(e.hash, newCapacity);//定位Hash桶
   e.next = newTable[i];//元素连接到桶中,这里相当于单链表的插入,总是插入在最前面
   newTable[i] = e;//newTable[i]的值总是最新插入的值 
   e = next;//继续下一个元素
    } } }

jdk 1.8 :

 final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

这个方法是将老数组中的数据逐个按照桶链表的形式遍历,重新计算后放入新的扩容数组中;在扩容之后我们发现,会将原来链表的元素进行倒置,而且在多线程的扩容过程中容易存在循环链表的问题,所以尽可能的减少扩容的过程,在使用前就要考虑初始容量的大小,让该条件不成立(size>=threshold)

注意: hashMap的数组的长度是2的幂次方,这样做的好处是什么;

如果length为2的次幂,其二进制表示就是100….0000;则length-1 转化为二进制必定是0111….11的
形式,在于h的二进制与操作效率会非常的快,而且空间不浪费;如果length不是2的次幂,比如
length为15,则length-1为14,对应的二进制为1110,再于h与操作,
最后一位都为0,所以0001,0011,0101,1001,1011,0111,1101这几个位置永远都不会存放元
素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味
着进一步增加了碰撞的几率,减慢了查询的效率!这样就会造成空间的浪费。

1.5 get操作:
//获取key值为key的元素值
 public V get(Object key) {
  if (key == null)//如果Key值为空,则获取对应的值,这里也可以看到,HashMap允许null的key,其内 部针对null的key有特殊的逻辑 
  return getForNullKey(); 
  Entry<K,V> entry = getEntry(key);//获取实体
   return null == entry ? null : entry.getValue();//判断是否为空,不为空,则获取对应的值 
   }//获取key为null的实体 
   private V getForNullKey() {
    if (size == 0) {//如果元素个数为0,则直接返回null 
    return null; }//key为null的元素存储在table的第0个位置
for (Entry<K,V> e = table[0]; e != null; e = e.next) { 
if (e.key == null)//判断是否为null
 return e.value;//返回其值
  }
  return null; }

get方法 通过key值返回对应的value;如果key为null,直接去table[0] 处检查。

//获取键值为key的元素
final Entry<K,V> getEntry(Object key) { 
if (size == 0) {//元素个数为0 
return null;//直接返回null
 }
 int hash = (key == null) ? 0 : hash(key);//获取key的Hash值
for (Entry<K,V> e = table[indexFor(hash, table.length)];//根据key和表的长度,定位到 Hash桶
 e != null; e = e.next) {//进行遍历 
  Object k;
   if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k))))//判断Hash值和对应的 key,合适则返回值
   return e; 
   }return null; }

可以看出,get的实现相对简单,key(hashcode)-> hash -> indexFor -> 最终索引位置,找到最终位置table[i],在查看是否有链表,遍历链表,通过key的eqauls方法来进行对比;要注意的是,有人觉得上面在定位到数组位置之后然后遍历链表的时候,e.hash == hash这个判断没必要,仅通过equals判断就可以。其实不然,试想一下,如果传入的key对象重写了equals方法却没有重写hashCode,而恰巧此对象定位到这个数组位置,如果仅仅用equals判断可能是相等的,但其hashCode和当前对象不一致,这种情况,根据Object的hashCode的约定,不能返回当前对象,而
应该返回null。

1.6 总结:
  • HashMap是基于哈希表的Map接口的实现。此实现提供所有可选的映射操作,并允许使用null
    值和null键。(除了非同步和允许使用null之外,HashMap类与Hashtable大致相同)此类不保
    证映射的顺序,特别是它不保证该顺序恒久不变(发生扩容时,元素位置会重新分配)。
  • 迭代collection视图所需的时间与HashMap实例的“容量”(桶的数量)及其大小(键-值映射关
    系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子
    设置得太低)。
  • HashMap的实例有两个参数影响其性能:初始容量和加载因子。容量是哈希表中桶的数量,
    初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多
    满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表
    进行rehash操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。通常,默认
    加载因子 (0.75) 在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但
    同时也增加了查询成本(在大多数HashMap 类的操作中,包括get和put操作,都反映了这一
    点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减
    少rehash操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生rehash操作。
    如果很多映射关系要存储在HashMap实例中,则相对于按需执行自动的rehash操作以增大表
    的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。
  • HashMap的实现不是同步的。如果在多线程操作下,应该使用
    Collections.synchronizedMap 方法来“包装”该映射。最好在创建时完成这一操作,以防
    止对映射进行意外的非同步访问,如下所示:
    Map m = Collections.synchronizedMap(new HashMap(…)); public static void main(String[] args) { HashMap<Person, String> map = new HashMap<>(); Person person = new Person(1234, “kang”); map.put(person, “25岁”); // get取出,从逻辑上讲应该能输出“25岁” System.out.println(“结果:” + map.get(new Person(1234, “kang”)));//注意这里是new一 个对象} } @Override public int hashCode() { return this.ID;//这里为了简单起见,直接将ID值作为hashcode的值 } }
  • 由所有此类的“collection 视图方法”所返回的迭代器都是快速失败 的:在迭代器创建之后,如
    果从结构上对映射进行修改,除非通过迭代器本身的 remove 方法,其他任何时间任何方式的
    修改,迭代器都将抛出 ConcurrentModificationException。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值