Java集合框架之Map--Hashtable和Properties源码分析

《Java集合框架之Map--Hashtable和Properties源码分析》

1、Hashtable 简介

和HashMap一样,Hashtable 也是一个散列表,它存储的内容是键值对(key-value)映射。

Hashtable 继承于Dictionary,实现了Map、Cloneable、java.io.Serializable接口。

Hashtable 的函数都是同步的,这意味着它是
线程安全的。它的key、value都不可以为null。此外,Hashtable中的
映射不是有序的

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

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

2、Hashtable 特点

* 1).
线程安全

* 2).
Key、Value均不能为null

* 3).
包含了一个Entry[]数组,而Entry又是一个链表,用来处理冲突。

* 4).每个Key对应了Entry数组中固定的位置(记为index),称为槽位(Slot)。槽位计算公式为: (key.hashCode() & 0x7FFFFFFF) % Entry[].length() 。

* 5).当Entry[]的实际元素数量(Count)超过了分配容量(Capacity)的75%时,新建一个Entry[]是原先的2倍,并重新Hash(rehash)。

     rehash的核心思路是,将旧Entry[]数组的元素重新计算槽位,散列到新Entry[]中。

api

synchronized void                clear()
synchronized Object              clone()
             boolean             contains(Object value)
synchronized boolean             containsKey(Object key)
synchronized boolean             containsValue(Object value)
synchronized Enumeration<V>      elements()
synchronized Set<Entry<K, V>>    entrySet()
synchronized boolean             equals(Object object)
synchronized V                   get(Object key)
synchronized int                 hashCode()
synchronized boolean             isEmpty()
synchronized Set<K>              keySet()
synchronized Enumeration<K>      keys()
synchronized V                   put(K key, V value)
synchronized void                putAll(Map<? extends K, ? extends V> map)
synchronized V                   remove(Object key)
synchronized int                 size()
synchronized String              toString()
synchronized Collection<V>       values()

3、源码分析

  Dictionary抽象类

public abstract class Dictionary<K,V> { 
    public Dictionary() {
    }

    abstract public int size();

    abstract public boolean isEmpty();

    abstract public Enumeration<K> keys();

    abstract public Enumeration<V> elements();

    abstract public V get(Object key);

    abstract public V put(K key, V value);

    abstract public V remove(Object key);
}

Entry类

private static class Entry<K,V> implements Map.Entry<K,V> {
    // 哈希值
    int hash;
    K key;
    V value;
    // 指向的下一个Entry,即链表的下一个节点
    Entry<K,V> next;

    // 构造函数
    protected Entry(int hash, K key, V value, Entry<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

    protected Object clone() {
        return new Entry<K,V>(hash, key, value,
              (next==null ? null : (Entry<K,V>) next.clone()));
    }

    public K getKey() {
        return key;
    }

    public V getValue() {
        return value;
    }

    // 设置value。若value是null,则抛出异常。
    public V setValue(V value) {
        if (value == null)
            throw new NullPointerException();

        V oldValue = this.value;
        this.value = value;
        return oldValue;
    }

    // 覆盖equals()方法,判断两个Entry是否相等。
    // 若两个Entry的key和value都相等,则认为它们相等。
    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry e = (Map.Entry)o;

        return (key==null ? e.getKey()==null : key.equals(e.getKey())) &&
           (value==null ? e.getValue()==null : value.equals(e.getValue()));
    }

    public int hashCode() {
        return hash ^ (value==null ? 0 : value.hashCode());
    }

    public String toString() {
        return key.toString()+"="+value.toString();
    }
}

属性

    /**
     * 保存hashtable里key-value的数组。  
     * Hashtable同样采用单链表解决冲突,每一个Entry本质上是一个单向链表 
     */
    private transient Entry<K,V>[] table;

    // Hashtable中键值对的数量
    private transient int count;

    // 阈值,用于判断是否需要调整Hashtable的容量(threshold = 容量*加载因子)
    private int threshold;

    // 加载因子 
    private float loadFactor;

    //Hashtable被改变的次数,用于fail-fast机制的实现 
    private transient int modCount = 0;

    // 序列版本号 
    private static final long serialVersionUID = 1421746759512286392L;

Hashtable主要对外方法

1、contains() 和 containsValue() 的作用都是判断Hashtable是否包含“值(value)”

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

    public synchronized boolean contains(Object value) {
        // Hashtable中“键值对”的value不能是null,
        // 若是null的话,抛出异常!
        if (value == null) {
            throw new NullPointerException();
        }

        // 从后向前遍历table数组中的元素(Entry)
        // 对于每个Entry(单向链表),逐个遍历,判断节点的值是否等于value
        Entry tab[] = table;
        for (int i = tab.length ; i-- > 0 ;) {
            for (Entry<K,V> e = tab[i] ; e != null ; e = e.next) {
                if (e.value.equals(value)) {
                    return true;
                }
            }
        }
        return false;
    }

2、containsKey() 的作用是判断Hashtable是否包含key

    public synchronized boolean containsKey(Object key) {
        Entry tab[] = table;
        int hash = key.hashCode();

        // 计算索引值,
        // % tab.length 的目的是防止数据越界
        int index = (hash & 0x7FFFFFFF) % tab.length;

        // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return true;
            }
        }
        return false;
    }

elements() 的作用是返回“所有value”的枚举对象

    // 获取Hashtable的枚举类对象
    private <T> Enumeration<T> getEnumeration(int type) {
        if (count == 0) {
            return (Enumeration<T>)emptyEnumerator;
        } else {
            return new Enumerator<T>(type, false);
        }
    }public synchronized Enumeration<V> elements() {
        return this.<V>getEnumeration(VALUES);
    }

    // 获取Hashtable的枚举类对象
    private <T> Enumeration<T> getEnumeration(int type) {
        if (count == 0) {
            return (Enumeration<T>)emptyEnumerator;
        } else {
            return new Enumerator<T>(type, false);
        }
    }

get() 的作用就是获取key对应的value,没有的话返回null。跟containKeys方法一样,只是返回值不同

    public synchronized V get(Object key) {
        Entry tab[] = table;
        int hash = key.hashCode();
        // 计算索引值,
        int index = (hash & 0x7FFFFFFF) % tab.length;
        // 找到“key对应的Entry(链表)”,然后在链表中找出“哈希值”和“键值”与key都相等的元素
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                return e.value;
            }
        }
        return null;
    }

put让Hashtable对象可以通过put()将“key-value”添加到Hashtable中。

    public synchronized V put(K key, V value) {
        // Hashtable中不能插入value为null的元素!!!
        if (value == null) {
            throw new NullPointerException();
        }

        // 若“Hashtable中已存在键为key的键值对”,
        // 则用“新的value”替换“旧的value”
        Entry tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
        for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                V old = e.value;
                e.value = value;
                return old;
            }
        }

        // 若“Hashtable中不存在键为key的键值对”,
        // (01) 将“修改统计数”+1
        modCount++;
        // (02) 若“Hashtable实际容量” > “阈值”(阈值=总的容量 * 加载因子)
        //  则调整Hashtable的大小
        if (count >= threshold) {
            // Rehash the table if the threshold is exceeded
            rehash();

            tab = table;
            index = (hash & 0x7FFFFFFF) % tab.length;
        }

        // (03) 将“Hashtable中index”位置的Entry(链表)保存到e中
        Entry<K,V> e = tab[index];
        // (04) 创建“新的Entry节点”,并将“新的Entry”插入“Hashtable的index位置”,并设置e为“新的Entry”的下一个元素(即“新Entry”为链表表头)。        
        tab[index] = new Entry<K,V>(hash, key, value, e);
        // (05) 将“Hashtable的实际容量”+1
        count++;
        return null;
    }

refesh()

    // rehash(): 再次hash。当Entry[]的实际存储数量占分配容量的约75%时,扩容并且重新计算各个对象的槽位
    static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 ;
    protected void rehash() {
        int oldCapacity = table.length;
        Entry[] oldMap = table;
        int newCapacity = (oldCapacity << 1) + 1; // 2倍+1
        Entry[] newMap = new Entry[newCapacity];
        threshold = (int)(newCapacity * loadFactor);
        table = newMap;

        for( int i=oldCapacity; i-- >0;){ //  i的取值范围为 [oldCapacity-1,0]
            for (Entry<K,V> old = oldMap[i]; old!=null;){ // 遍历旧Entry[]
                Entry<K,V> e = old;
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;     // 重新计算各个元素在新Entry[]中的槽位index。
                e.next = newMap[index]; // 已经存在槽位中的Entry放到当前元素的next中
                newMap[index]=e;     // 放到槽位中
                old = old.next;
            }
        }
    }

二、Properties类

先说如何使用这个类

    @Test
    public void testProperties(){
        InputStream inProperties = null;

        try {
            //--------------------------  1、读取.properties文件 ---------------------------------
            Properties properties1 = new Properties();
//            inProperties = new FileInputStream("test.properties");//通过文件路径获取

             /*利用反射读取配置文件dim.properties里的信息:调用getResourceAsStream 获取类路径下的文件对应的输入流*/
            inProperties = getClass().getClassLoader().getResourceAsStream("test.properties");
            properties1.load(inProperties);

            /*获取配置文件的内容,中文可能会出现乱码,需要转换一下,用GBK或者UTF-8  */
            String name = (new String(properties1.getProperty("name").getBytes("ISO-8859-1"), "GBK"));
            String age = properties1.getProperty("age");

            System.out.println("--------------- "+name+", "+age+" -------------------------");


            //---------------------------  2、读取.xml文件 -------------------------------
            Properties properties2 = new Properties();
            inProperties = getClass().getClassLoader().getResourceAsStream("test.xml");
            properties2.load(inProperties);
            properties2.list(System.out);//将属性文件中的键值对儿打印到控制台


            //---------------------------  3、将内容保存到文件中---------------------------------
            Properties prop = new Properties();
            prop.setProperty("one", "a");
            prop.setProperty("two", "b");
            prop.setProperty("three", "c"); //中文会乱码
            prop.storeToXML(new FileOutputStream("e:/test.xml"), "saveXML");//将键值对儿保存到XML文件中
            prop.store(new FileOutputStream("e:/test.properties"), "saveProperties");//将键值对儿保存到普通的属性文件中

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                assert inProperties != null;
                inProperties.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    原文作者:java集合源码分析
    原文地址: https://blog.csdn.net/oChangWen/article/details/51111294
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞