chap05-Dictionary / HashTable / Properties

《java collections》学习笔记

Dictionary以及其子类是key-value的数据结构,不像Vector使用index来寻找值,Dictonary使用key来查找值。

在这里插入图片描述

  • HashTable<K,V> : key和value都可以为任意Object类型。
  • Properties<String,String>: key和value只能为String


Dictionary

Dictionary是个抽象类,特殊的地方是:它多有的方法都是abstract,

至于为什么不设计为接口,据说是当时设计Dictionary类时,java语言还没有接口,当后续java语言支持接口后,这个问题也没有解决。

Dictionary 不允许key或者value 任一为空

APIs

public abstract class Dictionary<K,V> {

    public Dictionary() {
    }

    //返回 key的个数
    abstract public int size();

    //判断 key的个数是否 > 0
    abstract public boolean isEmpty();

    //返回 enumeration of the keys
    abstract public Enumeration<K> keys();

    //返回 enumeration of the values
    abstract public Enumeration<V> elements();

    //根据key 获取value
    abstract public V get(Object key);

    //新增key-value : key,或者 value为空时 抛出NPE
    abstract public V put(K key, V value);

    /**
     * key为空时,  抛出NPE
     * @param  key 待删除的key
     * @return     被删除的value
     */
    abstract public V remove(Object key);
}

由于Dictionary是个抽象类,所有的方法都是abstract的,所以我们从不直接使用Dictionary, 而是使用它的子类。接下来分析它的子类之一:HashTable.

Dictionary: key,value都不允许为空
HashTable: 继承自Dictionary,同Dictionary
HashMap: key,value 都可以为空。
HashSet: 内部使用HashMap,key可以为空
TreeSet: key不允许为空



HashTable

Hashtable是一个特殊Dictionary,它使用hash算法,将key转换为hash值,并用它来寻找dictionay中的value.

Hash算法

哈希表是一种提供几乎恒定时间插入和搜索的数据结构(时间复杂度为0(1)),这就意味着,无论数据量有多大,它都需要几乎相同的时间去定位元素。

当我们使用hashtable中的key−value键值队时,会通过hash算法keys转换为一个被称为hashcode的integer数值

hashCode()方法在Object类中定义,以生成哈希码,并且通常被子类重写

hash碰撞
当两个对象equals()时,是指使用equals()方法比较时,返回true. 两个不相等的对象,他们的hashcode可能相同,也可能不同。
当两个unequal的对象hashcode 相同时,被称为hash碰撞
当碰撞次数增加时,会很明显的降低插入和搜索的效率

下图是一个存在hash碰撞的输出存储结构。
在这里插入图片描述

存储过程
当对HashTable进行搜索时,首先将key转换为hashcode,然后进行某种算法定位到索引(index),如果有多个元素相同在散列表中的索引,必须遍历链表以查找具有特定键的元素。

假如有如下简单算法:

// 计算hashcode的过程
//1.首先查找各个字符的assci码的值
J = 74
o = 111
h = 104
n = 110

//2.计算各个名字的hashcode
John = 74*103 + 111*102 + 104*101 + 110*100 = 86250
Cary: 67*103 + 97*102 + 114*101 + 121*100 = 77961
Cody: 67*103 + 111*102 + 100*101 + 121*100 = 79221
Omar: 79*103 + 109*102 + 97*101 + 114*100 = 90984


//3.计算index
index = hashcode % arraySize;

假设我们将上述的names放置在一个长度为13(一般都为质数)的数组.它的存储结果如下图:
在这里插入图片描述

hashtable本质上的数据结构是一个 LinkedList[] ,具有相同的locate的元素放置在同一个链表。
当hash碰撞次数较多时,则具有相同locate的链表长度变长,查找元素时需要遍历整个链表,这严重降低了遍历的效率。
在jdk8中,hashmap为了解决这个问题,当链表的长度到达一定值(默认为8)后,会进行treefy


属性&构造函数

public class Hashtable<K,V>
    extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable {

    //实际上数据存储的结构: 是一个"链表的数组"
    private transient Entry<?,?>[] table;

    //记录所有元素的个数; 
    private transient int count;

    //元素个数阈值:通常 threshold = initialCapacity * loadFactor
    //当count > threshold 时,则需要将table[] 数组扩容,
    //扩容算法为:当前容量 * 2 + 1 ;
    private int threshold;

    //负载系数:默认值为0.75
    private float loadFactor;

    private transient int modCount = 0;


    public Hashtable() {
        this(11, 0.75f);
    }

    public Hashtable(int initialCapacity) {
        this(initialCapacity, 0.75f);
    }

    public Hashtable(Map<? extends K, ? extends V> t) {
        this(Math.max(2*t.size(), 11), 0.75f);
        putAll(t);
    }

    /**
     * @param  initialCapacity 是哈希表中"桶" 的数量
     * @param  loadFactor      负载系数,默认为 0.75
     */
    public Hashtable(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal Load: "+loadFactor);

        if (initialCapacity==0)
            initialCapacity = 1;
        this.loadFactor = loadFactor;
        table = new Entry<?,?>[initialCapacity];
        threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
    }
}

HashTable是线程安全的,基本上所有的方法都是synchronized

新增元素

put()

public synchronized V put(K key, V value) {
        if (value == null) {
            throw new NullPointerException();
        }

        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        //若hash为负数,转换为正数
        //定位 桶index
        int index = (hash & 0x7FFFFFFF) % tab.length;
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
       //遍历Entry(链表)
        for(; entry != null ; entry = entry.next) {
        	//判断key是否已经存在,若已经存在则更新值,返回原先的oldValue
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
		
        addEntry(hash, key, value, index);
        return null;
    }

?? hash & 0x7FFFFFFF的作用,为什么不用Math.abs(int)

// “-2^31”到“2^31-1”即“-2147483648”到“2147483647”
int a = 1 << 31;
System.out.println(a); // -2147483648
System.out.println(a & Integer.MAX_VALUE); // 0
System.out.println(Math.abs(a)); //-2147483648

addEntry()

private void addEntry(int hash, K key, V value, int index) {
        modCount++;

        Entry<?,?> tab[] = table;
         // count >= threshold ,给数组扩容
        if (count >= threshold) {
          //1.扩容
            rehash();

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

        // 创建一个新的Entry(链表中的节点)
        // tab[index] 总是指向链表中的“最后插入的Entry”,但最后插入的位置是链表:header位置;
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
		
		//元素个数自增1,在新增完成之后才增加1;  
		//说明到新增到第 threshold+1 个元素时,才可以触发上面的rehash()函数
        count++;
    }
   private static class Entry<K,V> implements Map.Entry<K,V> {
        final int hash;
        final K key;
        V value;
        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;
        }
}

假如先后向链表中插入: a,b,c,d 四个元素,
在链表中的结构为: d->c->b->a

整体元素个数count > threshold 时,就要对Entry[]数组进行扩容

rehash()

  protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;

        // 容量*2 + 1
        int newCapacity = (oldCapacity << 1) + 1;
       //newCapacity 与 MAX_ARRAY_SIZE 大小比较(略)

        //创建一个新的数组,数组长度为newCapacity,
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];

        modCount++;
        threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1);
        table = newMap;

        //填充newMap
        for (int i = oldCapacity ; i-- > 0 ;) {
     
            //获取oldMap[i]链表的header元素,进行遍历链表元素,重定位
            for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {
                //old为链表的header元素;
                Entry<K,V> e = old;

                old = old.next;

                //根据新的长度,计算新的index;
                int index = (e.hash & 0x7FFFFFFF) % newCapacity;

                //table[index] 总是指向桶的第一个元素的;
                //则将之前的header元素,设置为当前e.next
                e.next = (Entry<K,V>)newMap[index];
                
   			   //最后将e设为第一个元素
                newMap[index] = e;
            }
        }
    }

rehash()例,详解:
为了更好的理解rehash方法,接下来将分析一个例子:

   public void test01(){
        //
        // 默认 initialCapacity = 11, loadFactor = 0.75
        // threshold = 11 * 0.75 = 8
        // count >= threshold 触发rehash,即当count==8时,触发rehash,
        //而每次addEntry()完毕之后,才进行count++,即进行到第9个元素新增时,触发rehash
        Hashtable hashtable1 = new Hashtable();

        //创建一个lenth == 9的数组
        int[] array = {1,11,12,20,21,22,23,30,34};
        Arrays.stream(array).forEach(item ->{
            hashtable1.put(item, item);
        });
    }

当插入到第九个元素时,触发rehash条件,此时插入前的HashTable数据结构如下图:
在这里插入图片描述
rehash执行完之后,capacity变为23,变换后执行插入操作,数据变为如下结构:
在这里插入图片描述

putAll()

public synchronized void putAll(Map<? extends K, ? extends V> t) {
  for (Map.Entry<? extends K, ? extends V> e : t.entrySet())
         put(e.getKey(), e.getValue());
 }

删除元素

remove()

 public synchronized V remove(Object key) {
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;
       //首先定位到“桶”
        Entry<K,V> e = (Entry<K,V>)tab[index];
        
        //遍历桶,定位到指定的元素,并找到prev元素
        for(Entry<K,V> prev = null ; e != null ; prev = e, e = e.next) {
            if ((e.hash == hash) && e.key.equals(key)) {
                modCount++;
                if (prev != null) {
                	//将e.next 设置为prev.next; 即将e从 桶链表中移除;
                    prev.next = e.next;
                } else {
                    tab[index] = e.next;
                }
                count--;
                V oldValue = e.value;
                //设置为null,用于gc回收
                e.value = null;
				//返回删除的值
                return oldValue;
            }
        }
        return null;
    }

clear

    public synchronized void clear() {
        Entry<?,?> tab[] = table;
        modCount++;
        //遍历table,将table中所有桶header元素设为null;
        for (int index = tab.length; --index >= 0; )
            tab[index] = null;
	//设置元素个数为0
        count = 0;
    }

获取元素

public Object get(Object key)

public synchronized V get(Object key) {
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    //找到指定的“桶”
    int index = (hash & 0x7FFFFFFF) % tab.length;
    //遍历链表
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            return (V)e.value;
        }
    }
    //未找到,返回null;	
    return null;
}

通过keySet()遍历
demo

Iterator iterator = hashtable1.keySet().iterator();
 while(iterator.hasNext()){
     Object key = iterator.next();
     System.out.println(key +"->" +hashtable1.get(key));
 }

源码分析

// keySet().iterator()
//1.
public Set<K> keySet() {
    if (keySet == null)
        //1.1 创建内部类KeySet
        //1.2 调用KeySet.iterator()
        keySet = Collections.synchronizedSet(new KeySet(), this);
    return keySet;
}

//1.1 KeySet
private class KeySet extends AbstractSet<K> {
    //1.2
    public Iterator<K> iterator() {
        //1.3 执行Hashtable.getIterator(Keys)
        return getIterator(KEYS);
    }
}



//1.3 
private <T> Iterator<T> getIterator(int type) {
    if (count == 0) {
        return Collections.emptyIterator();
    } else {
        //3.返回内部类Enumerator
        return new Enumerator<>(type, true);
    }
}

Enumerator
Enumerator即实现了Enumeration接口,也实现了Iterator接口

private class Enumerator<T> implements Enumeration<T>, Iterator<T> {
        Entry<?,?>[] table = Hashtable.this.table;

        //记录下一次遍历的位置
        int index = table.length;
        
        //记录下一次遍历的对象
        Entry<?,?> entry;

        //记录上一次返回的entry
        Entry<?,?> lastReturned;

        //KEYS,VALUES, ENTRIES
        int type;

        /**
         * true -> Iterator  ====>只有在iterator遍历时才允许删除
         * false -> Enumeration
         */
        boolean iterator;

        protected int expectedModCount = modCount;

        Enumerator(int type, boolean iterator) {
            this.type = type;
            this.iterator = iterator;
        }

        //判断是否有元素:只需要判断每个桶的header元素是否为空即可;
        public boolean hasMoreElements() {
            Entry<?,?> e = entry;
            int i = index;
            Entry<?,?>[] t = table;
            //当e == null时,寻找到第一个不为null的桶的header元素.
            //当e != null时, 直接跳过while()往下执行;
            while (e == null && i > 0) {
                e = t[--i];
            }
            //修改下一次遍历的对象为: e
            entry = e;
            //修改下一次遍历的位置为: i
            index = i;
            return e != null;
        }

        public T nextElement() {
            //获取当前遍历的对象 entry;
            Entry<?,?> et = entry;
            int i = index;

            // 同hasMoreElements()逻辑 ----------
            Entry<?,?>[] t = table;
            while (et == null && i > 0) {
                et = t[--i];
            }
            entry = et;
            index = i;
            // ------------------------------------

            if (et != null) {
                //返回当前entry
                Entry<?,?> e = lastReturned = entry;

                //将entry元素的next 设置为下一次即将遍历的对象
                //!!!!注意, 这里不需要修改index,因为是在同一个桶内,遍历“列表”
                entry = e.next;

                //根据不同的类型返回不同的值
                return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
            }
            throw new NoSuchElementException("Hashtable Enumerator");
        }


        public boolean hasNext() {
            return hasMoreElements();
        }

        public T next() {
            //fast-fail机制
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            return nextElement();
        }

        public void remove() {
            //只有在iterator == true时才允许删除
            if (!iterator)
                throw new UnsupportedOperationException();
            if (lastReturned == null)
                throw new IllegalStateException("Hashtable Enumerator");
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

            //略.....
        }
    }

通过keys()遍历
例:

     Enumeration enumeration =  hashtable1.keys();
      while(enumeration.hasMoreElements()){
          Object key = enumeration.nextElement();
          System.out.println(key +"->" +hashtable1.get(key));
      }

源码

	public synchronized Enumeration<K> keys() {
    return this.<K>getEnumeration(KEYS);
}

private <T> Enumeration<T> getEnumeration(int type) {
    if (count == 0) {
        return Collections.emptyEnumeration();
    } else {
        return new Enumerator<>(type, false);
    }
}

遍历值:values(),elements(),entrySet()

public Collection<V> values() {
    if (values==null)
    	//同keySet(),最终返回:return new Enumerator<>(VALUES, true);
        values = Collections.synchronizedCollection(new ValueCollection(),
                                                    this);
    return values;
}

public synchronized Enumeration<V> elements() {
	//同同keys(),最终返回 new Enumerator<>(VALUES, false);
    return this.<V>getEnumeration(VALUES);
 }

public Set<Map.Entry<K,V>> entrySet() {
     if (entrySet==null)
     	 //最终返回 new Enumerator<>(ENTRIES, true);
         entrySet = Collections.synchronizedSet(new EntrySet(), this);
     return entrySet;
 }

containsKey(Object key),containsValue(Object value),contains(Object value)

public synchronized boolean containsKey(Object key) {
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    //定位到桶,
    int index = (hash & 0x7FFFFFFF) % tab.length;
    //遍历桶列表
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            return true;
        }
    }
    return false;
}


public boolean containsValue(Object value) {
    return contains(value);
}
 public synchronized boolean contains(Object value) {
    if (value == null) {
        throw new NullPointerException();
    }

    Entry<?,?> tab[] = table;
    //遍历各个桶
    for (int i = tab.length ; i-- > 0 ;) {
        //遍历桶中各个元素
        for (Entry<?,?> e = tab[i] ; e != null ; e = e.next) {
            if (e.value.equals(value)) {
                return true;
            }
        }
    }
    return false;
}

其他APIs

size相关

public synchronized int size() {
    return count;
 }
public synchronized boolean isEmpty() {
   return count == 0;
 }

equals()

public synchronized boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Map))
        return false;
    Map<?,?> t = (Map<?,?>) o;

    //元素个数相同
    if (t.size() != size())
        return false;

   Iterator<Map.Entry<K,V>> i = entrySet().iterator();
    while (i.hasNext()) {
        Map.Entry<K,V> e = i.next();
        K key = e.getKey();
        V value = e.getValue();
        if (value == null) {
            //value == null时, 若t不含有key,或者含有key但是value不为null,则返回false
            if (!(t.get(key)==null && t.containsKey(key)))
                return false;
        } else {
            //value != null时, 相同的key对应的value必须equal
            //隐含了另一个条件: 同时含有key;
            if (!value.equals(t.get(key)))
                return false;
        }
    }

    return true;
}

equals()总结一句话: 必须size()相同,且所有的key和value相同,但是(桶的个数,同内部的顺序)可以不同,

hashCode()
Hashtable的哈希码是sum(所有元素的哈希码)

Immutability

 Hashtable hashtable1 = new Hashtable();
 Collections.unmodifiableMap(hashtable1)


Properties

Properties继承自HashTable.与HashTable不同的是,Properties的key-value都只能是String类型.
但是由于它Hashtable的子类,因此可以直接调用Hashtable方法来存储其他对象类型
对象类型。
虽然如此,我们也应该避免使用这些方法,因为如果key或者value使用了其他类型,

  • 在使用如:list(),load(),save()等方法会出现异常;
  • 在使用:getProperty()时,获取为空.
properties.put(1, 1); //调用HashTable方法

System.out.println(properties.get(1)); //调用HashTable方法:  1
System.out.println(properties.getProperty("1")); //null, getProperty的key,value只能为String

//抛出异常: 
// java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
properties.list(System.out);

APIs

构造函数

public class Properties extends Hashtable<Object,Object> {

    protected Properties defaults;
  
    public Properties() {
        this(null);
    }

    //如果当前Properties没有key的值,会尝试从defaults中获取
    public Properties(Properties defaults) {
        this.defaults = defaults;
    }
}

getter,setter

public synchronized Object setProperty(String key, String value) {
   //调用父类Hashtable的方法
   return put(key, value);
}

public String getProperty(String key) {
    Object oval = super.get(key);
    String sval = (oval instanceof String) ? (String)oval : null;
    //若当前不存在key,尝试从defaults中获取
    return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;
}

//若不存在key,则返回默认值
public String getProperty(String key, String defaultValue) {
    String val = getProperty(key);
    return (val == null) ? defaultValue : val;
}

list
若key,或者value存在非String类型的值,则list()方法会抛出异常。。。

public void list(PrintStream out) {
   out.println("-- listing properties --");

   //新增一个hashtable, 来存放全量的 key-value;
   Hashtable<String,Object> h = new Hashtable<>();
   //递归所有defaults,获取所有的key-value
   enumerate(h);

   //默认认为keys都是
   for (Enumeration<String> e = h.keys() ; e.hasMoreElements() ;) {
       String key = e.nextElement();

       //将value强制转换为String
       String val = (String)h.get(key);
       if (val.length() > 40) {
           val = val.substring(0, 37) + "...";
       }
       out.println(key + "=" + val);
   }
}

private synchronized void enumerate(Hashtable<String,Object> h) {
   if (defaults != null) {
       defaults.enumerate(h);
   }
   for (Enumeration<?> e = keys() ; e.hasMoreElements() ;) {
       //强制将key转换为String
       String key = (String)e.nextElement();
       h.put(key, get(key));
   }
}

//同上list(PrintStream) 略..
public void list(PrintWriter out);

如:

properties.put(1, 1);  
properties.list(System.out);

后抛出如下异常
在这里插入图片描述

load(InputStream)
Properties的load(InputStream)方法支持从输入流中加载数据。

foo:bar
one
two
three=four
five six seven eight
nine ten
#ten
!ele

properties文件的分隔符支持:=,,space,table等。
上述properties加载后结果如下:
在这里插入图片描述
store()

    @Test
    public void testStore() throws IOException {
        Properties properties = new Properties();
        properties.setProperty("a", "A");
        //抛出java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
//        properties.put(1, "1"); 
        properties.store(new FileOutputStream("out.properties"),"this is comments");
    }

执行后结果:
在这里插入图片描述

System Properties

APIs

public final class System {
 	private static Properties props;
 	
 	public static String getProperty(String key);
    public static Properties getProperties() ;

    public static String setProperty(String key, String value);
    //替换所有的系统环境变量
    public static void setProperties(Properties props);
}

列出所有配置信息
System.getProperties().list(System.out); 结果如下图:
在这里插入图片描述

使用java -Dmsg=hello,moto Program命令,可以设置环境变量信息
在这里插入图片描述

PropertyResourceBundle

配置文件
在这里插入图片描述
java

public void testPropertyResourceBundle() throws IOException {
	ResourceBundle bundle = ResourceBundle
			.getBundle("i18n/message", Locale.ENGLISH);
	System.out.println(bundle.getString("name")); //Trump
	System.out.println(bundle.getString("welcome")); // 取自message.properties: hello,body

	ResourceBundle bundleZhCN = ResourceBundle
			.getBundle("i18n/message", new Locale("zh", "CN"));
	System.out.println(bundleZhCN.getString("name"));  //乱码
}

执行结果:
在这里插入图片描述

中文乱码问题
properties文件中使用unicode:
在这里插入图片描述

PropertyResourceBundle
它继承自ResourceBundle:
在这里插入图片描述

PropertyResourceBundle propertyResourceBundle = (PropertyResourceBundle) ResourceBundle
		.getBundle("i18n/message", Locale.SIMPLIFIED_CHINESE);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值