> 文档中心 > HashMap学习

HashMap学习


简介

Java为数据结构中的映射定义了一个接口java.util.Map

1、HashMap:它根据键的hashCode值存储数据,大多数情况下可以直接定位到它的值,因而具有很快的访问速度。HashMap最多只允许一条记录的键为null,允许多条记录的值为null。线程安全。如果需要满足线程安全,可以用 Collections的synchronizedMap方法使HashMap具有线程安全的能力,或者使用ConcurrentHashMap

2、Hashtable:Hashtable是遗留类,很多映射的常用功能与HashMap类似,不同的是它承自Dictionary类。线程安全。并发性不如ConcurrentHashMap,因为ConcurrentHashMap引入了分段锁。

3、LinkedHashMap:LinkedHashMap是HashMap的一个子类,保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。

4、TreeMap:TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。
在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。

内部实现

(1) 存储结构-字段
(2) 功能实现-方法

存储结构-字段

HashMap是数组+链表+红黑树(JDK1.8增加了红黑树部分)实现的。

这里需要讲明白两个问题:数据底层具体存储的是什么?这样的存储方式有什么优点呢?

HashMap类中有一个非常重要的字段,就是 Node[] table,即哈希桶数组

static class Node implements Map.Entry {   final int hash;    //用来定位数组索引位置      final K key;   V value;   Node next;   //链表的下一个node      Node(int hash, K key, V value, Node next) { ... }   public final K getKey(){ ... }   public final V getValue() { ... }   public final String toString() { ... }   public final int hashCode() { ... }   public final V setValue(V newValue) { ... }   public final boolean equals(Object o) { ... }}

Node是HashMap的一个内部类,实现了Map.Entry接口,本质是就是一个映射(键值对)。

默认容量和加载因子 

 HashMap有两个参数影响其性能:初始容量加载因子默认初始容量是16,加载因子是0.75。容量是哈希表中桶(Entry数组)的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,通过调用 rehash 方法将容量翻倍。

源码

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

默认的容量是16,而 threshold是16*0.75 = 12;相对准确的估算数据量,将极大的影响HashMap的性能,因为resize是一个重新分配的过程,耗时应该是里面最大的。加载因子较小,会有更多的空间空闲,我不知道这个0.75是不是一个折中方案。也许0.9也是一个不错的选择,特别是那些数据量虽然很大,但不是经常变化的地方,比如公司人员,城市列表等相对比较固定的数据。

小结

    (1) 扩容是一个特别耗性能的操作,所以当程序员在使用HashMap的时候,估算map的大小,初始化的时候给一个大致的数值,避免map进行频繁的扩容。

    (2) 负载因子是可以修改的,也可以大于1,但是建议不要轻易修改,除非情况非常特殊。

    (3) HashMap是线程不安全的,不要在并发的环境中同时操作HashMap,建议使用ConcurrentHashMap。

    (4) JDK1.8引入红黑树大程度优化了HashMap的性能。