zoukankan      html  css  js  c++  java
  • [源码解析]HashMap和HashTable的区别(源码分析解读)

    前言: 
    又是一个大好的周末, 可惜今天起来有点晚, 扒开HashMap和HashTable, 看看他们到底有什么区别吧.

    先来一段比较拗口的定义:

    Hashtable 的实例有两个参数影响其性能:初始容量 和 加载因子。容量 是哈希表中桶 的数量,初始容量 就是哈希表创建时的容量。注意,哈希表的状态为 open:在发生“哈希冲突”的情况下,单个桶会存储多个条目,这些条目必须按顺序搜索。加载因子 是对哈希表在其容量自动增加之前可以达到多满的一个尺度。初始容量和加载因子这两个参数只是对该实现的提示。关于何时以及是否调用 rehash 方法的具体细节则依赖于该实现。

      而HashTable是 基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。 此实现假定哈希函数将元素适   当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。迭代 collection 视图所需的时间与 HashMap 实例的“容量”(桶的数量)及其大小(键-值映射关系数)成比例。所以,如果迭代性能很重要,则不要将初始容量设置得太高(或将加载因子设置得太低)。
      

     

    一, 实例举证

     1     
     2     public static void main(String[] args) {
     3         Map<String, String> map = new HashMap<String, String>();
     4         map.put("a", "aaa");
     5         map.put("b", "bbb");
     6         map.put("c", "ccc");
     7         map.put("d", "ddd"); 
     8         Iterator<String> iterator = map.keySet().iterator();
     9         while (iterator.hasNext()) {
    10             Object key = iterator.next();
    11             System.out.println("map.get(key) is :" + map.get(key));
    12         }
    13 
    14         Hashtable<String, String> tab = new Hashtable<String, String>();
    15         tab.put("a", "aaa");
    16         tab.put("b", "bbb");
    17         tab.put("c", "ccc");
    18         tab.put("d", "ddd");  
    19         Iterator<String> iterator_1 = tab.keySet().iterator();
    20         while (iterator_1.hasNext()) {
    21             Object key = iterator_1.next();
    22             System.out.println("tab.get(key) is :" + tab.get(key));
    23         }
    24     }
    25 }

    首先上面有这么一段代码, 那么它的输出是什么呢? 

    可以看到, HashMap按照正常顺序输出, 而HashTable输出的顺序却有些诡异.

    2, 源码分析
    看到上面的结果, 那么我们就分别来看下HashMap和HashTable的源码吧.

    首先我要来灌输一些思想, 然后再根据这些定义的规则(前人总结出来的) 再去源码中一探究竟.

    1)HashTable是同步的,HashMap是非同步的
    HashTable中put和get方法:

     1 public synchronized V put(K key, V value) {
     2         // Make sure the value is not null
     3         if (value == null) {
     4             throw new NullPointerException();
     5         }
     6 
     7         // Makes sure the key is not already in the hashtable.
     8         Entry<?,?> tab[] = table;
     9         int hash = key.hashCode();
    10         int index = (hash & 0x7FFFFFFF) % tab.length;
    11         @SuppressWarnings("unchecked")
    12         Entry<K,V> entry = (Entry<K,V>)tab[index];
    13         for(; entry != null ; entry = entry.next) {
    14             if ((entry.hash == hash) && entry.key.equals(key)) {
    15                 V old = entry.value;
    16                 entry.value = value;
    17                 return old;
    18             }
    19         }
    20 
    21         addEntry(hash, key, value, index);
    22         return null;
    23     }

     

     1 public synchronized V get(Object key) {
     2         Entry<?,?> tab[] = table;
     3         int hash = key.hashCode();
     4         int index = (hash & 0x7FFFFFFF) % tab.length;
     5         for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
     6             if ((e.hash == hash) && e.key.equals(key)) {
     7                 return (V)e.value;
     8             }
     9         }
    10         return null;
    11     }

    HashMap中put和get方法:

    1 public V put(K key, V value) {
    2       return putVal(hash(key), key, value, false, true);
    3 }
    1 public V get(Object key) {
    2         Node<K,V> e;
    3         return (e = getNode(hash(key), key)) == null ? null : e.value;
    4 }

    从以上代码中就能显而易见的看到HashTable中的put和get方法是被synchronized修饰的, 这种做的区别呢? 
    由于非线程安全,效率上可能高于Hashtable. 如果当多个线程访问时, 我们可以使用HashTable或者通过Collections.synchronizedMap来同步HashMap。


    2)HashTable与HashMap实现的接口一致,但HashTable继承自Dictionary,而HashMap继承自AbstractMap;
    HashTable:

     

     HashMap:
     

     

    3)HashTable不允许null值(key和value都不可以) ,HashMap允许null值(key和value都可以)。

     在1中我们可以看到HashTable如果value为null就会直接抛出: throw new NullPointerException();
     那么再看看HashMap put value 具体做了什么?

    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;
    }

    由此可见, 并没有value值进行强制的nullCheck.

    4)HashTable有一个contains(Object value)功能和containsValue(Object value)功能一样。
    这里我们可以直接对比HashMap和HashTable有关Contains的方法:

    HashTable中的contains方法在HashMap中就被取消了, 那么我们来具体看下HashTable中的contains方法的作用: 

     1 public synchronized boolean contains(Object value) {
     2         if (value == null) {
     3             throw new NullPointerException();
     4         }
     5 
     6         Entry<?,?> tab[] = table;
     7         for (int i = tab.length ; i-- > 0 ;) {
     8             for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
     9                 if (e.value.equals(value)) {
    10                     return true;
    11                 }
    12             }
    13         }
    14         return false;
    15 }

    然后再看下HashTable中的containsValue方法:

    1 public boolean containsValue(Object value) {
    2         return contains(value);
    3 }

    这里就很明显了, contains方法其实做的事情就是containsValue, 里面将value值使用equals进行对比, 所以在HashTable中直接取消了contains方法而是使用containsValue代替.

    5)HashTable使用Enumeration进行遍历,HashMap使用Iterator进行遍历。


    首先是HashTable中:

     1 private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
     2     Entry<?,?>[] table = Hashtable.this.table;
     3     int index = table.length;
     4     Entry<?,?> entry;
     5     Entry<?,?> lastReturned;
     6     int type;
     7 
     8     /**
     9      * Indicates whether this Enumerator is serving as an Iterator
    10      * or an Enumeration.  (true -> Iterator).
    11      */
    12     boolean iterator;
    13 
    14     /**
    15      * The modCount value that the iterator believes that the backing
    16      * Hashtable should have.  If this expectation is violated, the iterator
    17      * has detected concurrent modification.
    18      */
    19     protected int expectedModCount = modCount;
    20 
    21     Enumerator(int type, boolean iterator) {
    22         this.type = type;
    23         this.iterator = iterator;
    24     }
    25 
    26     public boolean hasMoreElements() {
    27         Entry<?,?> e = entry;
    28         int i = index;
    29         Entry<?,?>[] t = table;
    30         /* Use locals for faster loop iteration */
    31         while (e == null && i > 0) {
    32             e = t[--i];
    33         }
    34         entry = e;
    35         index = i;
    36         return e != null;
    37     }
    38 
    39     @SuppressWarnings("unchecked")
    40     public T nextElement() {
    41         Entry<?,?> et = entry;
    42         int i = index;
    43         Entry<?,?>[] t = table;
    44         /* Use locals for faster loop iteration */
    45         while (et == null && i > 0) {
    46             et = t[--i];
    47         }
    48         entry = et;
    49         index = i;
    50         if (et != null) {
    51             Entry<?,?> e = lastReturned = entry;
    52             entry = e.next;
    53             return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
    54         }
    55         throw new NoSuchElementException("Hashtable Enumerator");
    56     }
    57 
    58     // Iterator methods
    59     public boolean hasNext() {
    60         return hasMoreElements();
    61     }
    62 
    63     public T next() {
    64         if (modCount != expectedModCount)
    65             throw new ConcurrentModificationException();
    66         return nextElement();
    67     }
    68 
    69     public void remove() {
    70         if (!iterator)
    71             throw new UnsupportedOperationException();
    72         if (lastReturned == null)
    73             throw new IllegalStateException("Hashtable Enumerator");
    74         if (modCount != expectedModCount)
    75             throw new ConcurrentModificationException();
    76 
    77         synchronized(Hashtable.this) {
    78             Entry<?,?>[] tab = Hashtable.this.table;
    79             int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length;
    80 
    81             @SuppressWarnings("unchecked")
    82             Entry<K,V> e = (Entry<K,V>)tab[index];
    83             for(Entry<K,V> prev = null; e != null; prev = e, e = e.next) {
    84                 if (e == lastReturned) {
    85                     modCount++;
    86                     expectedModCount++;
    87                     if (prev == null)
    88                         tab[index] = e.next;
    89                     else
    90                         prev.next = e.next;
    91                     count--;
    92                     lastReturned = null;
    93                     return;
    94                 }
    95             }
    96             throw new ConcurrentModificationException();
    97         }
    98     }
    99 }
    View Code

    然后是HashMap中:

     1 abstract class HashIterator {
     2         Node<K,V> next;        // next entry to return
     3         Node<K,V> current;     // current entry
     4         int expectedModCount;  // for fast-fail
     5         int index;             // current slot
     6 
     7         HashIterator() {
     8             expectedModCount = modCount;
     9             Node<K,V>[] t = table;
    10             current = next = null;
    11             index = 0;
    12             if (t != null && size > 0) { // advance to first entry
    13                 do {} while (index < t.length && (next = t[index++]) == null);
    14             }
    15         }
    16 
    17         public final boolean hasNext() {
    18             return next != null;
    19         }
    20 
    21         final Node<K,V> nextNode() {
    22             Node<K,V>[] t;
    23             Node<K,V> e = next;
    24             if (modCount != expectedModCount)
    25                 throw new ConcurrentModificationException();
    26             if (e == null)
    27                 throw new NoSuchElementException();
    28             if ((next = (current = e).next) == null && (t = table) != null) {
    29                 do {} while (index < t.length && (next = t[index++]) == null);
    30             }
    31             return e;
    32         }
    33 
    34         public final void remove() {
    35             Node<K,V> p = current;
    36             if (p == null)
    37                 throw new IllegalStateException();
    38             if (modCount != expectedModCount)
    39                 throw new ConcurrentModificationException();
    40             current = null;
    41             K key = p.key;
    42             removeNode(hash(key), key, null, false, false);
    43             expectedModCount = modCount;
    44         }
    45     }
    46 
    47     final class KeyIterator extends HashIterator
    48         implements Iterator<K> {
    49         public final K next() { return nextNode().key; }
    50     }
    51 
    52     final class ValueIterator extends HashIterator
    53         implements Iterator<V> {
    54         public final V next() { return nextNode().value; }
    55     }
    56 
    57     final class EntryIterator extends HashIterator
    58         implements Iterator<Map.Entry<K,V>> {
    59         public final Map.Entry<K,V> next() { return nextNode(); }
    60     }
    View Code

    废弃的接口:Enumeration
    Enumeration接口是JDK1.0时推出的,是最好的迭代输出接口,最早使用Vector(现在推荐使用ArrayList)时就是使用Enumeration接口进行输出。虽然Enumeration是一个旧的类,但是在JDK1.5之后为Enumeration类进行了扩充,增加了泛型的操作应用。

    Enumeration接口常用的方法有hasMoreElements()(判断是否有下一个值)和 nextElement()(取出当前元素),这些方法的功能跟Iterator类似,只是Iterator中存在删除数据的方法,而此接口不存在删除操作。

    为什么还要继续使用Enumeration接口
    Enumeration和Iterator接口功能相似,而且Iterator的功能还比Enumeration多,那么为什么还要使用Enumeration?这是因为java的发展经历了很长时间,一些比较古老的系统或者类库中的方法还在使用Enumeration接口,因此为了兼容,还是需要使用Enumeration。

    下面给出HashTable和HashMap的几种遍历方式:

     1 public class Person {
     2     private int age;
     3     private String name;
     4     private String email;
     5     public int getAge() {
     6         return age;
     7     }
     8     public void setAge(int age) {
     9         this.age = age;
    10     }
    11     public String getName() {
    12         return name;
    13     }
    14     public void setName(String name) {
    15         this.name = name;
    16     }
    17     public String getEmail() {
    18         return email;
    19     }
    20     public void setEmail(String email) {
    21         this.email = email;
    22     }
    23     @Override
    24     public String toString() {
    25         return "Person [age=" + age + ", name=" + name + ", email=" + email + "]";
    26     }
    27 }
    Person.java
      1 public class Test2 {
      2     public static void main(String[] args) {
      3         Person person1 = new Person();
      4         person1.setAge(34);
      5         person1.setName("Jacky");
      6         person1.setEmail("Jacky@gmail.com"); 
      7 
      8         Person person2 = new Person();
      9         person2.setAge(23);
     10         person2.setName("Ajay");
     11         person2.setEmail("Ajay@gmail.com"); 
     12 
     13         Person person3 = new Person();
     14         person3.setAge(12);
     15         person3.setName("Bill");
     16         person3.setEmail("Bill@gmail.com"); 
     17 
     18         Person person4 = new Person();
     19         person4.setAge(23);
     20         person4.setName("Gace");
     21         person4.setEmail("Gace@gmail.com");
     22 
     23         Person person5 = new Person();
     24         person5.setAge(45);
     25         person5.setName("Jim");
     26         person5.setEmail("Jim@gmail.com");
     27 
     28         Hashtable<String, Person> ht = new Hashtable<String, Person>();
     29         ht.put("1", person1);
     30         ht.put("2", person2);
     31         ht.put("3", person3);
     32         ht.put("4", person4);
     33         ht.put("5", person5);
     34         
     35         HashMap<String, Person> hm = new HashMap<String, Person>();
     36         hm.put("1", person1);
     37         hm.put("2", person2);
     38         hm.put("3", person3);
     39         hm.put("4", person4);
     40         hm.put("5", person5);
     41         
     42         //HashTable 遍历方式:
     43         //第一种遍历方式, 使用key
     44         Enumeration<String> keys = ht.keys();
     45         while (keys.hasMoreElements()) {
     46             String key = (String) keys.nextElement();
     47             System.out.println("HashTable 的 key 是 : " + key + ", Value 是 : "+ht.get(key));
     48         }
     49         
     50         
     51         //第二种方式:使用elements()
     52         Enumeration<Person> elements = ht.elements();
     53         while (elements.hasMoreElements()) {
     54             Person person = (Person) elements.nextElement();
     55             System.out.println(person);
     56         }
     57         
     58         //第三种方式:使用keySet()
     59         Iterator<String> iterator = ht.keySet().iterator();
     60         while (iterator.hasNext()) {
     61             String key = (String) iterator.next();
     62             Person value = hm.get(key);
     63             
     64             System.out.println(key + " " + value);
     65         }
     66         
     67         //第四种方式:使用entrySet
     68         Iterator<Entry<String, Person>> iterator2 = ht.entrySet().iterator();
     69         while (iterator2.hasNext()) {
     70             Map.Entry<String, Person> entry = (Map.Entry<String, Person>) iterator2.next();
     71             
     72             String key = entry.getKey();
     73             Person value = entry.getValue();
     74             
     75             System.out.println(key + " " + value);
     76         }
     77         
     78         //HashTable
     79         //第一种方式
     80         Iterator<Entry<String, Person>> iterator3 = hm.entrySet().iterator();
     81         while (iterator3.hasNext()) {
     82             Map.Entry<String, Person> entry = (Map.Entry<String, Person>) iterator3.next();
     83             
     84             String key = entry.getKey();
     85             Person value = entry.getValue();
     86             
     87             System.out.println(key + " " + value);
     88         }
     89         
     90         //第二种方式
     91         Iterator<String> iterator4 = hm.keySet().iterator();
     92         // the second method to travel the map
     93         while (iterator4.hasNext()) {
     94             String key = (String) iterator4.next();
     95             Person value = hm.get(key);
     96             
     97             System.out.println(key + " " + value);
     98         }
     99     }
    100 }
    Test.java

    6)HashTable中hash数组默认大小是11,增加的方式是 old*2+1。HashMap中hash数组的默认大小是16,而且一定是2的指数。

    HashMap:

    1 /**
    2      * The default initial capacity - MUST be a power of two.
    3      */
    4     static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    HashTable:通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查找某个条目的时间(在大多数 Hashtable 操作中,包括 get 和 put 操作,都反映了这一点)。

    1  // 默认构造函数。
    2 public Hashtable() {
    3     // 默认构造函数,指定的容量大小是11;加载因子是0.75
    4     this(11, 0.75f);
    5 }

     

    7)哈希值的使用不同
    HashTable:,HashTable直接使用对象的hashCode

    1 int hash = key.hashCode();
    2 int index = (hash & 0x7FFFFFFF) % tab.length;

    HashMap:HashMap重新计算hash值,而且用与代替求模:

    1 int hash = hash(k);
    2 int i = indexFor(hash, table.length);
    3 static int hash(Object x) {
    4 h ^= (h >>> 20) ^ (h >>> 12);
    5      return h ^ (h >>> 7) ^ (h >>> 4);
    6 }
    7 static int indexFor(int h, int length) {
    8 return h & (length-1);
    9 }

     

    3,其他关联
    3.1HashMap与HashSet的关系

    a、HashSet底层是采用HashMap实现的:

    1 public HashSet() {
    2     map = new HashMap<E,Object>();
    3 }

    b、调用HashSet的add方法时,实际上是向HashMap中增加了一行(key-value对),该行的key就是向HashSet增加的那个对象,该行的value就是一个Object类型的常量。

    1 private static final Object PRESENT = new Object(); public boolean add(E e) { 
    2     return map.put(e, PRESENT)==null; 
    3 } 
    4 public boolean remove(Object o) { 
    5     return map.remove(o)==PRESENT; 
    6 }

    3.2 HashMap 和 ConcurrentHashMap 的关系

    关于这部分内容建议自己去翻翻源码,ConcurrentHashMap 也是一种线程安全的集合类,他和HashTable也是有区别的,主要区别就是加锁的粒度以及如何加锁,ConcurrentHashMap 的加锁粒度要比HashTable更细一点。将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

    更多请参考: http://www.hollischuang.com/archives/82


    4. HashTable源码奉上

     

      1 package java.util;
      2 
      3 import java.io.*;
      4 
      5 public class Hashtable<K, V> extends Dictionary<K, V> implements Map<K, V>, Cloneable, java.io.Serializable {
      6 
      7     // Hashtable保存key-value的数组。
      8     // Hashtable是采用拉链法实现的,每一个Entry本质上是一个单向链表
      9     private transient Entry[] table;
     10 
     11     // Hashtable中元素的实际数量
     12     private transient int count;
     13 
     14     // 阈值,用于判断是否需要调整Hashtable的容量(threshold = 容量*加载因子)
     15     private int threshold;
     16 
     17     // 加载因子
     18     private float loadFactor;
     19 
     20     // Hashtable被改变的次数
     21     private transient int modCount = 0;
     22 
     23     // 序列版本号
     24     private static final long serialVersionUID = 1421746759512286392L;
     25 
     26     // 指定“容量大小”和“加载因子”的构造函数
     27     public Hashtable(int initialCapacity, float loadFactor) {
     28         if (initialCapacity < 0)
     29             throw new IllegalArgumentException("Illegal Capacity: " + initialCapacity);
     30         if (loadFactor <= 0 || Float.isNaN(loadFactor))
     31             throw new IllegalArgumentException("Illegal Load: " + loadFactor);
     32 
     33         if (initialCapacity == 0)
     34             initialCapacity = 1;
     35         this.loadFactor = loadFactor;
     36         table = new Entry[initialCapacity];
     37         threshold = (int) (initialCapacity * loadFactor);
     38     }
     39 
     40     // 指定“容量大小”的构造函数
     41     public Hashtable(int initialCapacity) {
     42         this(initialCapacity, 0.75f);
     43     }
     44 
     45     // 默认构造函数。
     46     public Hashtable() {
     47         // 默认构造函数,指定的容量大小是11;加载因子是0.75
     48         this(11, 0.75f);
     49     }
     50 
     51     // 包含“子Map”的构造函数
     52     public Hashtable(Map<? extends K, ? extends V> t) {
     53         this(Math.max(2 * t.size(), 11), 0.75f);
     54         // 将“子Map”的全部元素都添加到Hashtable中
     55         putAll(t);
     56     }
     57 
     58     public synchronized int size() {
     59         return count;
     60     }
     61 
     62     public synchronized boolean isEmpty() {
     63         return count == 0;
     64     }
     65 
     66     // 返回“所有key”的枚举对象
     67     public synchronized Enumeration<K> keys() {
     68         return this.<K>getEnumeration(KEYS);
     69     }
     70 
     71     // 返回“所有value”的枚举对象
     72     public synchronized Enumeration<V> elements() {
     73         return this.<V>getEnumeration(VALUES);
     74     }
     75 
     76     // 判断Hashtable是否包含“值(value)”
     77     public synchronized boolean contains(Object value) {
     78         // Hashtable中“键值对”的value不能是null,
     79         // 若是null的话,抛出异常!
     80         if (value == null) {
     81             throw new NullPointerException();
     82         }
     83 
     84         // 从后向前遍历table数组中的元素(Entry)
     85         // 对于每个Entry(单向链表),逐个遍历,判断节点的值是否等于value
     86         Entry tab[] = table;
     87         for (int i = tab.length; i-- > 0;) {
     88             for (Entry<K, V> e = tab[i]; e != null; e = e.next) {
     89                 if (e.value.equals(value)) {
     90                     return true;
     91                 }
     92             }
     93         }
     94         return false;
     95     }
     96 
     97     public boolean containsValue(Object value) {
     98         return contains(value);
     99     }
    100 
    101     // 判断Hashtable是否包含key
    102     public synchronized boolean containsKey(Object key) {
    103         Entry tab[] = table;
    104         int hash = key.hashCode();
    105         // 计算索引值,
    106         // % tab.length 的目的是防止数据越界
    107         int index = (hash & 0x7FFFFFFF) % tab.length;
    108         // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素
    109         for (Entry<K, V> e = tab[index]; e != null; e = e.next) {
    110             if ((e.hash == hash) && e.key.equals(key)) {
    111                 return true;
    112             }
    113         }
    114         return false;
    115     }
    116 
    117     // 返回key对应的value,没有的话返回null
    118     public synchronized V get(Object key) {
    119         Entry tab[] = table;
    120         int hash = key.hashCode();
    121         // 计算索引值,
    122         int index = (hash & 0x7FFFFFFF) % tab.length;
    123         // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素
    124         for (Entry<K, V> e = tab[index]; e != null; e = e.next) {
    125             if ((e.hash == hash) && e.key.equals(key)) {
    126                 return e.value;
    127             }
    128         }
    129         return null;
    130     }
    131 
    132     // 调整Hashtable的长度,将长度变成原来的(2倍+1)
    133     // (01) 将“旧的Entry数组”赋值给一个临时变量。
    134     // (02) 创建一个“新的Entry数组”,并赋值给“旧的Entry数组”
    135     // (03) 将“Hashtable”中的全部元素依次添加到“新的Entry数组”中
    136     protected void rehash() {
    137         int oldCapacity = table.length;
    138         Entry[] oldMap = table;
    139 
    140         int newCapacity = oldCapacity * 2 + 1;
    141         Entry[] newMap = new Entry[newCapacity];
    142 
    143         modCount++;
    144         threshold = (int) (newCapacity * loadFactor);
    145         table = newMap;
    146 
    147         for (int i = oldCapacity; i-- > 0;) {
    148             for (Entry<K, V> old = oldMap[i]; old != null;) {
    149                 Entry<K, V> e = old;
    150                 old = old.next;
    151 
    152                 int index = (e.hash & 0x7FFFFFFF) % newCapacity;
    153                 e.next = newMap[index];
    154                 newMap[index] = e;
    155             }
    156         }
    157     }
    158 
    159     // 将“key-value”添加到Hashtable中
    160     public synchronized V put(K key, V value) {
    161         // Hashtable中不能插入value为null的元素!!!
    162         if (value == null) {
    163             throw new NullPointerException();
    164         }
    165 
    166         // 若“Hashtable中已存在键为key的键值对”,
    167         // 则用“新的value”替换“旧的value”
    168         Entry tab[] = table;
    169         int hash = key.hashCode();
    170         int index = (hash & 0x7FFFFFFF) % tab.length;
    171         for (Entry<K, V> e = tab[index]; e != null; e = e.next) {
    172             if ((e.hash == hash) && e.key.equals(key)) {
    173                 V old = e.value;
    174                 e.value = value;
    175                 return old;
    176             }
    177         }
    178 
    179         // 若“Hashtable中不存在键为key的键值对”,
    180         // (01) 将“修改统计数”+1
    181         modCount++;
    182         // (02) 若“Hashtable实际容量” > “阈值”(阈值=总的容量 * 加载因子)
    183         // 则调整Hashtable的大小
    184         if (count >= threshold) {
    185             // Rehash the table if the threshold is exceeded
    186             rehash();
    187 
    188             tab = table;
    189             index = (hash & 0x7FFFFFFF) % tab.length;
    190         }
    191 
    192         // (03) 将“Hashtable中index”位置的Entry(链表)保存到e中
    193         Entry<K, V> e = tab[index];
    194         // (04)
    195         // 创建“新的Entry节点”,并将“新的Entry”插入“Hashtable的index位置”,并设置e为“新的Entry”的下一个元素(即“新Entry”为链表表头)。
    196         tab[index] = new Entry<K, V>(hash, key, value, e);
    197         // (05) 将“Hashtable的实际容量”+1
    198         count++;
    199         return null;
    200     }
    201 
    202     // 删除Hashtable中键为key的元素
    203     public synchronized V remove(Object key) {
    204         Entry tab[] = table;
    205         int hash = key.hashCode();
    206         int index = (hash & 0x7FFFFFFF) % tab.length;
    207         // 找到“key对应的Entry(链表)”
    208         // 然后在链表中找出要删除的节点,并删除该节点。
    209         for (Entry<K, V> e = tab[index], prev = null; e != null; prev = e, e = e.next) {
    210             if ((e.hash == hash) && e.key.equals(key)) {
    211                 modCount++;
    212                 if (prev != null) {
    213                     prev.next = e.next;
    214                 } else {
    215                     tab[index] = e.next;
    216                 }
    217                 count--;
    218                 V oldValue = e.value;
    219                 e.value = null;
    220                 return oldValue;
    221             }
    222         }
    223         return null;
    224     }
    225 
    226     // 将“Map(t)”的中全部元素逐一添加到Hashtable中
    227     public synchronized void putAll(Map<? extends K, ? extends V> t) {
    228         for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
    229             put(e.getKey(), e.getValue());
    230     }
    231 
    232     // 清空Hashtable
    233     // 将Hashtable的table数组的值全部设为null
    234     public synchronized void clear() {
    235         Entry tab[] = table;
    236         modCount++;
    237         for (int index = tab.length; --index >= 0;)
    238             tab[index] = null;
    239         count = 0;
    240     }
    241 
    242     // 克隆一个Hashtable,并以Object的形式返回。
    243     public synchronized Object clone() {
    244         try {
    245             Hashtable<K, V> t = (Hashtable<K, V>) super.clone();
    246             t.table = new Entry[table.length];
    247             for (int i = table.length; i-- > 0;) {
    248                 t.table[i] = (table[i] != null) ? (Entry<K, V>) table[i].clone() : null;
    249             }
    250             t.keySet = null;
    251             t.entrySet = null;
    252             t.values = null;
    253             t.modCount = 0;
    254             return t;
    255         } catch (CloneNotSupportedException e) {
    256             // this shouldn't happen, since we are Cloneable
    257             throw new InternalError();
    258         }
    259     }
    260 
    261     public synchronized String toString() {
    262         int max = size() - 1;
    263         if (max == -1)
    264             return "{}";
    265 
    266         StringBuilder sb = new StringBuilder();
    267         Iterator<Map.Entry<K, V>> it = entrySet().iterator();
    268 
    269         sb.append('{');
    270         for (int i = 0;; i++) {
    271             Map.Entry<K, V> e = it.next();
    272             K key = e.getKey();
    273             V value = e.getValue();
    274             sb.append(key == this ? "(this Map)" : key.toString());
    275             sb.append('=');
    276             sb.append(value == this ? "(this Map)" : value.toString());
    277 
    278             if (i == max)
    279                 return sb.append('}').toString();
    280             sb.append(", ");
    281         }
    282     }
    283 
    284     // 获取Hashtable的枚举类对象
    285     // 若Hashtable的实际大小为0,则返回“空枚举类”对象;
    286     // 否则,返回正常的Enumerator的对象。(Enumerator实现了迭代器和枚举两个接口)
    287     private <T> Enumeration<T> getEnumeration(int type) {
    288         if (count == 0) {
    289             return (Enumeration<T>) emptyEnumerator;
    290         } else {
    291             return new Enumerator<T>(type, false);
    292         }
    293     }
    294 
    295     // 获取Hashtable的迭代器
    296     // 若Hashtable的实际大小为0,则返回“空迭代器”对象;
    297     // 否则,返回正常的Enumerator的对象。(Enumerator实现了迭代器和枚举两个接口)
    298     private <T> Iterator<T> getIterator(int type) {
    299         if (count == 0) {
    300             return (Iterator<T>) emptyIterator;
    301         } else {
    302             return new Enumerator<T>(type, true);
    303         }
    304     }
    305 
    306     // Hashtable的“key的集合”。它是一个Set,意味着没有重复元素
    307     private transient volatile Set<K> keySet = null;
    308     // Hashtable的“key-value的集合”。它是一个Set,意味着没有重复元素
    309     private transient volatile Set<Map.Entry<K, V>> entrySet = null;
    310     // Hashtable的“key-value的集合”。它是一个Collection,意味着可以有重复元素
    311     private transient volatile Collection<V> values = null;
    312 
    313     // 返回一个被synchronizedSet封装后的KeySet对象
    314     // synchronizedSet封装的目的是对KeySet的所有方法都添加synchronized,实现多线程同步
    315     public Set<K> keySet() {
    316         if (keySet == null)
    317             keySet = Collections.synchronizedSet(new KeySet(), this);
    318         return keySet;
    319     }
    320 
    321     // Hashtable的Key的Set集合。
    322     // KeySet继承于AbstractSet,所以,KeySet中的元素没有重复的。
    323     private class KeySet extends AbstractSet<K> {
    324         public Iterator<K> iterator() {
    325             return getIterator(KEYS);
    326         }
    327 
    328         public int size() {
    329             return count;
    330         }
    331 
    332         public boolean contains(Object o) {
    333             return containsKey(o);
    334         }
    335 
    336         public boolean remove(Object o) {
    337             return Hashtable.this.remove(o) != null;
    338         }
    339 
    340         public void clear() {
    341             Hashtable.this.clear();
    342         }
    343     }
    344 
    345     // 返回一个被synchronizedSet封装后的EntrySet对象
    346     // synchronizedSet封装的目的是对EntrySet的所有方法都添加synchronized,实现多线程同步
    347     public Set<Map.Entry<K, V>> entrySet() {
    348         if (entrySet == null)
    349             entrySet = Collections.synchronizedSet(new EntrySet(), this);
    350         return entrySet;
    351     }
    352 
    353     // Hashtable的Entry的Set集合。
    354     // EntrySet继承于AbstractSet,所以,EntrySet中的元素没有重复的。
    355     private class EntrySet extends AbstractSet<Map.Entry<K, V>> {
    356         public Iterator<Map.Entry<K, V>> iterator() {
    357             return getIterator(ENTRIES);
    358         }
    359 
    360         public boolean add(Map.Entry<K, V> o) {
    361             return super.add(o);
    362         }
    363 
    364         // 查找EntrySet中是否包含Object(0)
    365         // 首先,在table中找到o对应的Entry(Entry是一个单向链表)
    366         // 然后,查找Entry链表中是否存在Object
    367         public boolean contains(Object o) {
    368             if (!(o instanceof Map.Entry))
    369                 return false;
    370             Map.Entry entry = (Map.Entry) o;
    371             Object key = entry.getKey();
    372             Entry[] tab = table;
    373             int hash = key.hashCode();
    374             int index = (hash & 0x7FFFFFFF) % tab.length;
    375 
    376             for (Entry e = tab[index]; e != null; e = e.next)
    377                 if (e.hash == hash && e.equals(entry))
    378                     return true;
    379             return false;
    380         }
    381 
    382         // 删除元素Object(0)
    383         // 首先,在table中找到o对应的Entry(Entry是一个单向链表)
    384         // 然后,删除链表中的元素Object
    385         public boolean remove(Object o) {
    386             if (!(o instanceof Map.Entry))
    387                 return false;
    388             Map.Entry<K, V> entry = (Map.Entry<K, V>) o;
    389             K key = entry.getKey();
    390             Entry[] tab = table;
    391             int hash = key.hashCode();
    392             int index = (hash & 0x7FFFFFFF) % tab.length;
    393 
    394             for (Entry<K, V> e = tab[index], prev = null; e != null; prev = e, e = e.next) {
    395                 if (e.hash == hash && e.equals(entry)) {
    396                     modCount++;
    397                     if (prev != null)
    398                         prev.next = e.next;
    399                     else
    400                         tab[index] = e.next;
    401 
    402                     count--;
    403                     e.value = null;
    404                     return true;
    405                 }
    406             }
    407             return false;
    408         }
    409 
    410         public int size() {
    411             return count;
    412         }
    413 
    414         public void clear() {
    415             Hashtable.this.clear();
    416         }
    417     }
    418 
    419     // 返回一个被synchronizedCollection封装后的ValueCollection对象
    420     // synchronizedCollection封装的目的是对ValueCollection的所有方法都添加synchronized,实现多线程同步
    421     public Collection<V> values() {
    422         if (values == null)
    423             values = Collections.synchronizedCollection(new ValueCollection(), this);
    424         return values;
    425     }
    426 
    427     // Hashtable的value的Collection集合。
    428     // ValueCollection继承于AbstractCollection,所以,ValueCollection中的元素可以重复的。
    429     private class ValueCollection extends AbstractCollection<V> {
    430         public Iterator<V> iterator() {
    431             return getIterator(VALUES);
    432         }
    433 
    434         public int size() {
    435             return count;
    436         }
    437 
    438         public boolean contains(Object o) {
    439             return containsValue(o);
    440         }
    441 
    442         public void clear() {
    443             Hashtable.this.clear();
    444         }
    445     }
    446 
    447     // 重新equals()函数
    448     // 若两个Hashtable的所有key-value键值对都相等,则判断它们两个相等
    449     public synchronized boolean equals(Object o) {
    450         if (o == this)
    451             return true;
    452 
    453         if (!(o instanceof Map))
    454             return false;
    455         Map<K, V> t = (Map<K, V>) o;
    456         if (t.size() != size())
    457             return false;
    458 
    459         try {
    460             // 通过迭代器依次取出当前Hashtable的key-value键值对
    461             // 并判断该键值对,存在于Hashtable(o)中。
    462             // 若不存在,则立即返回false;否则,遍历完“当前Hashtable”并返回true。
    463             Iterator<Map.Entry<K, V>> i = entrySet().iterator();
    464             while (i.hasNext()) {
    465                 Map.Entry<K, V> e = i.next();
    466                 K key = e.getKey();
    467                 V value = e.getValue();
    468                 if (value == null) {
    469                     if (!(t.get(key) == null && t.containsKey(key)))
    470                         return false;
    471                 } else {
    472                     if (!value.equals(t.get(key)))
    473                         return false;
    474                 }
    475             }
    476         } catch (ClassCastException unused) {
    477             return false;
    478         } catch (NullPointerException unused) {
    479             return false;
    480         }
    481 
    482         return true;
    483     }
    484 
    485     // 计算Hashtable的哈希值
    486     // 若 Hashtable的实际大小为0 或者 加载因子<0,则返回0。
    487     // 否则,返回“Hashtable中的每个Entry的key和value的异或值 的总和”。
    488     public synchronized int hashCode() {
    489         int h = 0;
    490         if (count == 0 || loadFactor < 0)
    491             return h; // Returns zero
    492 
    493         loadFactor = -loadFactor; // Mark hashCode computation in progress
    494         Entry[] tab = table;
    495         for (int i = 0; i < tab.length; i++)
    496             for (Entry e = tab[i]; e != null; e = e.next)
    497                 h += e.key.hashCode() ^ e.value.hashCode();
    498         loadFactor = -loadFactor; // Mark hashCode computation complete
    499 
    500         return h;
    501     }
    502 
    503     // java.io.Serializable的写入函数
    504     // 将Hashtable的“总的容量,实际容量,所有的Entry”都写入到输出流中
    505     private synchronized void writeObject(java.io.ObjectOutputStream s) throws IOException {
    506         // Write out the length, threshold, loadfactor
    507         s.defaultWriteObject();
    508 
    509         // Write out length, count of elements and then the key/value objects
    510         s.writeInt(table.length);
    511         s.writeInt(count);
    512         for (int index = table.length - 1; index >= 0; index--) {
    513             Entry entry = table[index];
    514 
    515             while (entry != null) {
    516                 s.writeObject(entry.key);
    517                 s.writeObject(entry.value);
    518                 entry = entry.next;
    519             }
    520         }
    521     }
    522 
    523     // java.io.Serializable的读取函数:根据写入方式读出
    524     // 将Hashtable的“总的容量,实际容量,所有的Entry”依次读出
    525     private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
    526         // Read in the length, threshold, and loadfactor
    527         s.defaultReadObject();
    528 
    529         // Read the original length of the array and number of elements
    530         int origlength = s.readInt();
    531         int elements = s.readInt();
    532 
    533         // Compute new size with a bit of room 5% to grow but
    534         // no larger than the original size. Make the length
    535         // odd if it's large enough, this helps distribute the entries.
    536         // Guard against the length ending up zero, that's not valid.
    537         int length = (int) (elements * loadFactor) + (elements / 20) + 3;
    538         if (length > elements && (length & 1) == 0)
    539             length--;
    540         if (origlength > 0 && length > origlength)
    541             length = origlength;
    542 
    543         Entry[] table = new Entry[length];
    544         count = 0;
    545 
    546         // Read the number of elements and then all the key/value objects
    547         for (; elements > 0; elements--) {
    548             K key = (K) s.readObject();
    549             V value = (V) s.readObject();
    550             // synch could be eliminated for performance
    551             reconstitutionPut(table, key, value);
    552         }
    553         this.table = table;
    554     }
    555 
    556     private void reconstitutionPut(Entry[] tab, K key, V value) throws StreamCorruptedException {
    557         if (value == null) {
    558             throw new java.io.StreamCorruptedException();
    559         }
    560         // Makes sure the key is not already in the hashtable.
    561         // This should not happen in deserialized version.
    562         int hash = key.hashCode();
    563         int index = (hash & 0x7FFFFFFF) % tab.length;
    564         for (Entry<K, V> e = tab[index]; e != null; e = e.next) {
    565             if ((e.hash == hash) && e.key.equals(key)) {
    566                 throw new java.io.StreamCorruptedException();
    567             }
    568         }
    569         // Creates the new entry.
    570         Entry<K, V> e = tab[index];
    571         tab[index] = new Entry<K, V>(hash, key, value, e);
    572         count++;
    573     }
    574 
    575     // Hashtable的Entry节点,它本质上是一个单向链表。
    576     // 也因此,我们才能推断出Hashtable是由拉链法实现的散列表
    577     private static class Entry<K, V> implements Map.Entry<K, V> {
    578         // 哈希值
    579         int hash;
    580         K key;
    581         V value;
    582         // 指向的下一个Entry,即链表的下一个节点
    583         Entry<K, V> next;
    584 
    585         // 构造函数
    586         protected Entry(int hash, K key, V value, Entry<K, V> next) {
    587             this.hash = hash;
    588             this.key = key;
    589             this.value = value;
    590             this.next = next;
    591         }
    592 
    593         protected Object clone() {
    594             return new Entry<K, V>(hash, key, value, (next == null ? null : (Entry<K, V>) next.clone()));
    595         }
    596 
    597         public K getKey() {
    598             return key;
    599         }
    600 
    601         public V getValue() {
    602             return value;
    603         }
    604 
    605         // 设置value。若value是null,则抛出异常。
    606         public V setValue(V value) {
    607             if (value == null)
    608                 throw new NullPointerException();
    609 
    610             V oldValue = this.value;
    611             this.value = value;
    612             return oldValue;
    613         }
    614 
    615         // 覆盖equals()方法,判断两个Entry是否相等。
    616         // 若两个Entry的key和value都相等,则认为它们相等。
    617         public boolean equals(Object o) {
    618             if (!(o instanceof Map.Entry))
    619                 return false;
    620             Map.Entry e = (Map.Entry) o;
    621 
    622             return (key == null ? e.getKey() == null : key.equals(e.getKey()))
    623                     && (value == null ? e.getValue() == null : value.equals(e.getValue()));
    624         }
    625 
    626         public int hashCode() {
    627             return hash ^ (value == null ? 0 : value.hashCode());
    628         }
    629 
    630         public String toString() {
    631             return key.toString() + "=" + value.toString();
    632         }
    633     }
    634 
    635     private static final int KEYS = 0;
    636     private static final int VALUES = 1;
    637     private static final int ENTRIES = 2;
    638 
    639     // Enumerator的作用是提供了“通过elements()遍历Hashtable的接口” 和
    640     // “通过entrySet()遍历Hashtable的接口”。因为,它同时实现了 “Enumerator接口”和“Iterator接口”。
    641     private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
    642         // 指向Hashtable的table
    643         Entry[] table = Hashtable.this.table;
    644         // Hashtable的总的大小
    645         int index = table.length;
    646         Entry<K, V> entry = null;
    647         Entry<K, V> lastReturned = null;
    648         int type;
    649 
    650         // Enumerator是 “迭代器(Iterator)” 还是 “枚举类(Enumeration)”的标志
    651         // iterator为true,表示它是迭代器;否则,是枚举类。
    652         boolean iterator;
    653 
    654         // 在将Enumerator当作迭代器使用时会用到,用来实现fail-fast机制。
    655         protected int expectedModCount = modCount;
    656 
    657         Enumerator(int type, boolean iterator) {
    658             this.type = type;
    659             this.iterator = iterator;
    660         }
    661 
    662         // 从遍历table的数组的末尾向前查找,直到找到不为null的Entry。
    663         public boolean hasMoreElements() {
    664             Entry<K, V> e = entry;
    665             int i = index;
    666             Entry[] t = table;
    667             /* Use locals for faster loop iteration */
    668             while (e == null && i > 0) {
    669                 e = t[--i];
    670             }
    671             entry = e;
    672             index = i;
    673             return e != null;
    674         }
    675 
    676         // 获取下一个元素
    677         // 注意:从hasMoreElements() 和nextElement() 可以看出“Hashtable的elements()遍历方式”
    678         // 首先,从后向前的遍历table数组。table数组的每个节点都是一个单向链表(Entry)。
    679         // 然后,依次向后遍历单向链表Entry。
    680         public T nextElement() {
    681             Entry<K, V> et = entry;
    682             int i = index;
    683             Entry[] t = table;
    684             /* Use locals for faster loop iteration */
    685             while (et == null && i > 0) {
    686                 et = t[--i];
    687             }
    688             entry = et;
    689             index = i;
    690             if (et != null) {
    691                 Entry<K, V> e = lastReturned = entry;
    692                 entry = e.next;
    693                 return type == KEYS ? (T) e.key : (type == VALUES ? (T) e.value : (T) e);
    694             }
    695             throw new NoSuchElementException("Hashtable Enumerator");
    696         }
    697 
    698         // 迭代器Iterator的判断是否存在下一个元素
    699         // 实际上,它是调用的hasMoreElements()
    700         public boolean hasNext() {
    701             return hasMoreElements();
    702         }
    703 
    704         // 迭代器获取下一个元素
    705         // 实际上,它是调用的nextElement()
    706         public T next() {
    707             if (modCount != expectedModCount)
    708                 throw new ConcurrentModificationException();
    709             return nextElement();
    710         }
    711 
    712         // 迭代器的remove()接口。
    713         // 首先,它在table数组中找出要删除元素所在的Entry,
    714         // 然后,删除单向链表Entry中的元素。
    715         public void remove() {
    716             if (!iterator)
    717                 throw new UnsupportedOperationException();
    718             if (lastReturned == null)
    719                 throw new IllegalStateException("Hashtable Enumerator");
    720             if (modCount != expectedModCount)
    721                 throw new ConcurrentModificationException();
    722 
    723             synchronized (Hashtable.this) {
    724                 Entry[] tab = Hashtable.this.table;
    725                 int index = (lastReturned.hash & 0x7FFFFFFF) % tab.length;
    726 
    727                 for (Entry<K, V> e = tab[index], prev = null; e != null; prev = e, e = e.next) {
    728                     if (e == lastReturned) {
    729                         modCount++;
    730                         expectedModCount++;
    731                         if (prev == null)
    732                             tab[index] = e.next;
    733                         else
    734                             prev.next = e.next;
    735                         count--;
    736                         lastReturned = null;
    737                         return;
    738                     }
    739                 }
    740                 throw new ConcurrentModificationException();
    741             }
    742         }
    743     }
    744 
    745     private static Enumeration emptyEnumerator = new EmptyEnumerator();
    746     private static Iterator emptyIterator = new EmptyIterator();
    747 
    748     // 空枚举类
    749     // 当Hashtable的实际大小为0;此时,又要通过Enumeration遍历Hashtable时,返回的是“空枚举类”的对象。
    750     private static class EmptyEnumerator implements Enumeration<Object> {
    751 
    752         EmptyEnumerator() {
    753         }
    754 
    755         // 空枚举类的hasMoreElements() 始终返回false
    756         public boolean hasMoreElements() {
    757             return false;
    758         }
    759 
    760         // 空枚举类的nextElement() 抛出异常
    761         public Object nextElement() {
    762             throw new NoSuchElementException("Hashtable Enumerator");
    763         }
    764     }
    765 
    766     // 空迭代器
    767     // 当Hashtable的实际大小为0;此时,又要通过迭代器遍历Hashtable时,返回的是“空迭代器”的对象。
    768     private static class EmptyIterator implements Iterator<Object> {
    769 
    770         EmptyIterator() {
    771         }
    772 
    773         public boolean hasNext() {
    774             return false;
    775         }
    776 
    777         public Object next() {
    778             throw new NoSuchElementException("Hashtable Iterator");
    779         }
    780 
    781         public void remove() {
    782             throw new IllegalStateException("Hashtable Iterator");
    783         }
    784 
    785     }
    786 }
    View Code

     

     

     

  • 相关阅读:
    ==和equals区别
    如何创建一个不可变类
    mysql用户的创建和授权
    事务
    Java知识点检测
    Redis
    正则表达式中match的用法
    rfind的用法
    找出文件夹里所有的文件路径
    合并多个pdf文件
  • 原文地址:https://www.cnblogs.com/wang-meng/p/5720805.html
Copyright © 2011-2022 走看看