• HashMap和Hashtable的区别源码对比(一)


    一些数据我们需要使用一一对应的键值对的方式来存储,那么我们就可以使用Map,Map就是用来存储“键(key)-值(value) 对”的,是通过键来标识做唯一区分,键是具有唯一性,不可重复的。

    Map作为一个接口,实现它的对象有HashMap、HashTable等。

    public interface Map{}

     下面就来看一下HashMap和HashTable

    HashMap类的定义

    1. public class HashMap extends AbstractMap
    2. implements Map, Cloneable, Serializable {}

    HashTable类的定义

    1. public class Hashtable
    2. extends Dictionary
    3. implements Map, Cloneable, java.io.Serializable {}

    从上面两个类的定义可以看到HashMap与HashTable两个类相同的地方是:

    HashMap与HashTable都实现了Map、Cloneable(支持被克隆)、Serializable(支持序列化)接口;

    HashMap与HashTable不同之处是:

    1、HashMap继承自AbstractMap类;而Hashtable继承自Dictionary类,但Dictionary这个类已经过时了,新的实现应该实现Map接口,而不是扩展这个类(在Dictionary类的源码中有标注,请看下面源码)。

    1. /**
    2. * The Dictionary class is the abstract parent of any
    3. * class, such as Hashtable, which maps keys to values.
    4. * Every key and every value is an object. In any one Dictionary
    5. * object, every key is associated with at most one value. Given a
    6. * Dictionary and a key, the associated element can be looked up.
    7. * Any non-null object can be used as a key and as a value.
    8. *

    9. * As a rule, the equals method should be used by
    10. * implementations of this class to decide if two keys are the same.
    11. *

    12. * NOTE: This class is obsolete. New implementations should
    13. * implement the Map interface, rather than extending this class.
    14. * 注意:这个类已经过时了。新的实现应该实现Map接口,而不是扩展这个类。
    15. *
    16. * @author unascribed
    17. * @see java.util.Map
    18. * @see java.lang.Object#equals(java.lang.Object)
    19. * @see java.lang.Object#hashCode()
    20. * @see java.util.Hashtable
    21. * @since JDK1.0
    22. */
    23. public abstract
    24. class Dictionary {}

    2、HashMap中key(键)和value(值)都可以为null;HashTable中key(键)和value(值)都不能为空,否则会报空指针异常。

    1. HashMap:基于哈希表的映射接口实现。该实现提供了所有可选的映射操作,并允许值为null和键为null。(HashMap类大致相当于Hashtable,除了它是不同步的并且允许空值。)这个类不保证映射的顺序;
    2. 特别是,它不能保证顺序随时间的推移保持不变。
    3. /**
    4. * Hash table based implementation of the Map interface. This
    5. * implementation provides all of the optional map operations, and permits
    6. * null values and the null key. (The HashMap
    7. * class is roughly equivalent to Hashtable, except that it is
    8. * unsynchronized and permits nulls.) This class makes no guarantees as to
    9. * the order of the map; in particular, it does not guarantee that the order
    10. * will remain constant over time.
    11. */
    12. HashTable:这个类实现了一个哈希表,它将键映射到值。任何非null对象都可以用作键或值。
    13. /**
    14. * This class implements a hash table, which maps keys to values. Any
    15. * non-null object can be used as a key or as a value.
    16. */

    3、HashMap和HashTable的初始容量不同,HashMap的初始容量是16,HashTable的初始容量是11;但是它们的负载因子相同默认都是:0.75。

    (1)构造一个空的HashMap,其默认初始容量(16)和默认负载因子(0.75)。

    1. /**
    2. * Constructs an empty HashMap with the default initial capacity
    3. * (16) and the default load factor (0.75).
    4. */
    5. public HashMap() {
    6. this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
    7. }

    (2)构造一个新的空哈希表,其默认初始容量(11)和负载系数(0.75)。

    1. /**
    2. * Constructs a new, empty hashtable with a default initial capacity (11)
    3. * and load factor (0.75).
    4. */
    5. public Hashtable() {
    6. this(11, 0.75f);
    7. }

    4、当容量不够时需要扩容,他们的扩容机制不同。

    当已用容量>总容量 * 负载因子时,HashMap 扩容规则为当前容量*2;Hashtable 扩容规则为当前容量*2 +1。

    (1)HashMap扩容:

    初始化或加倍表的大小。如果为空,则按照字段阈值中持有的初始容量目标分配。否则,因为我们使用的是2的幂展开,所以每个bin中的元素要么必须保持相同的索引,要么在新表中以2的幂偏移量移动。

    1. /**
    2. * Initializes or doubles table size. If null, allocates in
    3. * accord with initial capacity target held in field threshold.
    4. * Otherwise, because we are using power-of-two expansion, the
    5. * elements from each bin must either stay at same index, or move
    6. * with a power of two offset in the new table.
    7. *
    8. * @return the table
    9. */
    10. final Node[] resize() {
    11. Node[] oldTab = table;
    12. int oldCap = (oldTab == null) ? 0 : oldTab.length;
    13. int oldThr = threshold;
    14. int newCap, newThr = 0;
    15. if (oldCap > 0) {
    16. if (oldCap >= MAXIMUM_CAPACITY) {
    17. threshold = Integer.MAX_VALUE;
    18. return oldTab;
    19. }
    20. else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
    21. oldCap >= DEFAULT_INITIAL_CAPACITY)
    22. newThr = oldThr << 1; // double threshold
    23. }
    24. else if (oldThr > 0) // initial capacity was placed in threshold
    25. newCap = oldThr;
    26. else { // zero initial threshold signifies using defaults
    27. newCap = DEFAULT_INITIAL_CAPACITY;
    28. newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    29. }
    30. if (newThr == 0) {
    31. float ft = (float)newCap * loadFactor;
    32. newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
    33. (int)ft : Integer.MAX_VALUE);
    34. }
    35. threshold = newThr;
    36. @SuppressWarnings({"rawtypes","unchecked"})
    37. Node[] newTab = (Node[])new Node[newCap];
    38. table = newTab;
    39. if (oldTab != null) {
    40. for (int j = 0; j < oldCap; ++j) {
    41. Node e;
    42. if ((e = oldTab[j]) != null) {
    43. oldTab[j] = null;
    44. if (e.next == null)
    45. newTab[e.hash & (newCap - 1)] = e;
    46. else if (e instanceof TreeNode)
    47. ((TreeNode)e).split(this, newTab, j, oldCap);
    48. else { // preserve order
    49. Node loHead = null, loTail = null;
    50. Node hiHead = null, hiTail = null;
    51. Node next;
    52. do {
    53. next = e.next;
    54. if ((e.hash & oldCap) == 0) {
    55. if (loTail == null)
    56. loHead = e;
    57. else
    58. loTail.next = e;
    59. loTail = e;
    60. }
    61. else {
    62. if (hiTail == null)
    63. hiHead = e;
    64. else
    65. hiTail.next = e;
    66. hiTail = e;
    67. }
    68. } while ((e = next) != null);
    69. if (loTail != null) {
    70. loTail.next = null;
    71. newTab[j] = loHead;
    72. }
    73. if (hiTail != null) {
    74. hiTail.next = null;
    75. newTab[j + oldCap] = hiHead;
    76. }
    77. }
    78. }
    79. }
    80. }
    81. return newTab;
    82. }

    (2)HashTable扩容:

    增加这个散列表的容量并在内部重新组织它,以便更有效地容纳和访问它的条目。当散列表中的键数超过该散列表的容量和负载因子时,将自动调用此方法。

    1. /**
    2. * Increases the capacity of and internally reorganizes this
    3. * hashtable, in order to accommodate and access its entries more
    4. * efficiently. This method is called automatically when the
    5. * number of keys in the hashtable exceeds this hashtable's capacity
    6. * and load factor.
    7. */
    8. @SuppressWarnings("unchecked")
    9. protected void rehash() {
    10. int oldCapacity = table.length;
    11. HashtableEntry[] oldMap = table;
    12. // overflow-conscious code
    13. int newCapacity = (oldCapacity << 1) + 1;
    14. if (newCapacity - MAX_ARRAY_SIZE > 0) {
    15. if (oldCapacity == MAX_ARRAY_SIZE)
    16. // Keep running with MAX_ARRAY_SIZE buckets
    17. return;
    18. newCapacity = MAX_ARRAY_SIZE;
    19. }
    20. HashtableEntry[] newMap = new HashtableEntry[newCapacity];
    21. modCount++;
    22. threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    23. table = newMap;
    24. for (int i = oldCapacity ; i-- > 0 ;) {
    25. for (HashtableEntry old = (HashtableEntry)oldMap[i] ; old != null ; ) {
    26. HashtableEntry e = old;
    27. old = old.next;
    28. int index = (e.hash & 0x7FFFFFFF) % newCapacity;
    29. e.next = (HashtableEntry)newMap[index];
    30. newMap[index] = e;
    31. }
    32. }
    33. }

    5、HashMap线程不安全,HashTable线程安全

    Hashtable是同步(synchronized)的,在它的一些方法里都使用了synchronized关键字,由此保证其线程安全,适用于多线程环境。

    而hashmap不是同步的,适用于单线程环境。

    由于Hashtable是同步的(synchronized)线程安全的,所以在单线程环境下它比HashMap要慢。如果不需要同步,只需要单一线程,那么使用HashMap性能要比Hashtable好。 


    注解:
            sychronized意味着在一次仅有一个线程能够更改Hashtable。就是说任何线程要更新Hashtable时要首先获得同步锁,其它线程要等到同步锁被释放之后才能再次获得同步锁更新Hashtable。比如Hashtable 提供的几个主要方法(如下源码)中,如 put(),get(), contains(), remove() 等。不会出现两个线程同时对数据进行操作的情况,因此保证了线程安全性,但是也大大的降低了执行效率。

            在Java中,可以使用synchronized关键字来标记一个方法或者代码块,当某个线程调用该对象的synchronized方法或者访问synchronized代码块时,这个线程便获得了该对象的锁,其他线程暂时无法访问这个方法,只有等待这个方法执行完毕或者代码块执行完毕,这个线程才会释放该对象的锁,其他线程才能执行这个方法或者代码块。

    1. public synchronized V put(K key, V value){}
    2. public synchronized V get(Object key) {}
    3. public synchronized void putAll(Map t) {}
    4. public synchronized boolean contains(Object value) {}
    5. public synchronized boolean containsKey(Object key) {}
    6. public synchronized V remove(Object key) {}
    7. public synchronized void clear() {}
    8. public synchronized String toString() {}

    下面看一下HashMap与HashTable的基本使用方法

    1. //定义的键key-值value的具体数据类型为String
    2. HashMap stringMap = new HashMap<>();
    3. stringMap.put("","");
    4. stringMap.get("");
    5. stringMap.putAll(new HashMap());
    6. stringMap.containsKey("");
    7. stringMap.containsValue("");
    8. stringMap.remove("");
    9. stringMap.clear();
    10. stringMap.values();
    11. stringMap.toString();
    12. //没有定义具体数据类型
    13. Map map = new HashMap();
    14. map.put("","");
    15. HashMap hashMap = new HashMap();
    16. hashMap.put(1,1);
    17. //数据类型可根据实际使用更改
    18. Hashtable hashtable = new Hashtable<>();
    19. hashtable.put("1","1");
    20. hashtable.get("");
    21. hashtable.putAll(new HashMap());
    22. hashtable.contains("");
    23. hashtable.containsKey("");
    24. hashtable.containsValue("");
    25. hashtable.remove("");
    26. hashtable.clear();
    27. hashtable.values();
    28. hashtable.toString();

    后面将根据源码来对比一下HashMap与HashTable上面列出的各个方法,以及其它的一些不同之处,待更新下一篇文章……

  • 相关阅读:
    李峋同款爱心代码
    Vue路由重复点击报错解决
    “一带一路”十周年:用英语讲好中华传统故事
    axios+vue 请求时如何携带cookie
    .NET 8使用日志功能以及自定义日志提供程序
    45-命令行基础操作
    node版本管理工具nvm的使用
    Web(二)html5基础-文档头部(知识训练和编程训练)
    路由查找原理
    LeetCode 双周赛 99,纯纯送分场!
  • 原文地址:https://blog.csdn.net/u013184970/article/details/126937476