HashMap与HashTable区别

Hashtable实现类

Hashtable:线程安全的,不允许null的键或值;是线程安全的但是Hashtable线程安全的策略实现代价却太大了,简单粗暴,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把大锁。多线程访问时候,只要有一个线程访问或操作该对象,那其他线程只能阻塞,相当于将所有的操作串行化,在竞争激烈的并发场景中性能就会非常差

基础编程

Map<Integer,String> map=new Hashtable<>();

map.put(11,"zhangsan");

map.put(22,"lisi");

map.put(11,"wangwu");

for(Integer tmp:map.keySet()){

    String val=map.get(tmp);

    System.out.println(tmp+"-->"+val);

}

String removed = map.remove(11);

System.out.println(removed);

System.out.println(map.size());

map.clear();



//存储空键和空值的问题

        Map<Integer,String> map=new Hashtable<>();

//        Map<Integer,String> map=new HashMap<>();

        map.put(11,null);//NullPointerException

//       map.put(null,"lisis");//Hashtable中空键会有NullPointerException

        System.out.println(map.size());

类定义

/**

Since: 1.0  古老的类,目前不再推荐使用

*/

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

数据存储结构

private transient Entry<?,?>[] table;  具体的存储使用仍旧和HashMap一致,数组+链表



private static class Entry<K,V> implements Map.Entry<K,V> {

    final int hash;  缓存节点的hash值

    final K key;  key值

    V value;  value值

    Entry<K,V> next;  单向链表

构造器

public Hashtable() {

    this(11, 0.75f);  默认的初始化容积为11,默认加载因子0.75

}

public Hashtable(int initialCapacity) {

    this(initialCapacity, 0.75f);

}

public Hashtable(int initialCapacity, float loadFactor) {

    if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);  初始化容积小于0则运行时异常

    if (loadFactor <= 0 || Float.isNaN(loadFactor)) 如果负载因子小于等于0则运行异常,一般建议负载因子取值范围为(0,1)之间,事实上也可以大于等于1

        throw new IllegalArgumentException("Illegal Load: "+loadFactor);

    if (initialCapacity==0) 如果初始化容积为0则默认为1

        initialCapacity = 1;

    this.loadFactor = loadFactor;

    table = new Entry<?,?>[initialCapacity]; 立即初始化数组

    threshold = (int)Math.min(initialCapacity * loadFactor, MAX_ARRAY_SIZE + 1); 扩容阈值,取值为[初始化容积*负载因子]和Integer.MAX_VALUE - 8之间较小的值

}

成员方法

public synchronized V put(K key, V value)

public synchronized V remove(Object key) 

... ...

绝大部分的方法上都有synchronized,线程安全,会影响并发性,但是提高数据的安全性

public synchronized V put(K key, V value) {

    if (value == null)  throw new NullPointerException();  不允许值为null,否则异常

    Entry<?,?> tab[] = table;

    int hash = key.hashCode();  调用key对象的成员方法hashCode获取key的hash码值

    int index = (hash & 0x7FFFFFFF) % tab.length; 去除所获取hash码值的符号位,并对数组的长度进行求余,实际上就是获取key所对应的桶位置

    Entry<K,V> entry = (Entry<K,V>)tab[index]; 获取数组元素,也就是单向链的首节点

    for(; entry != null ; entry = entry.next) { 遍历桶对应的单向链

        if ((entry.hash == hash) && entry.key.equals(key)) {  当单向链上某个节点上的hash值相等并且equals为true时进行节点数据的覆盖。【要求定义键对应类型时当equals为true时hashCode值必须相等】

            V old = entry.value;

            entry.value = value;

            return old;

        }

    }

    addEntry(hash, key, value, index); 如果单向链上不存在相等的key则执行添加操作

    return null ;

}



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

    Entry<?,?> tab[] = table;  临时存储数组

    if (count >= threshold) { 如果当前存储的元素个数大于等于扩容阈值则进行扩容处理

        rehash();  扩容数组,新容积=老容积*2+1

        tab = table;

        hash = key.hashCode();  获取key对应的hash码值

        index = (hash & 0x7FFFFFFF) % tab.length;  根据hash码值通过对数组长度求余的方式获取存放位置的下标

    }

    Entry<K,V> e = (Entry<K,V>) tab[index];

    tab[index] = new Entry<>(hash, key, value, e); 将数据存储到数组中

    count++;

    modCount++;

}



protected void rehash() {

    int oldCapacity = table.length; 获取原始数组的长度

    Entry<?,?>[] oldMap = table;  缓存数组

    int newCapacity = (oldCapacity << 1) + 1;  新容积=原始容积*2+1

    if (newCapacity - MAX_ARRAY_SIZE > 0) { 新容积是否越界,如果超出Integer.MAX_VALUE - 8值,则新容积为Integer.MAX_VALUE - 8

        if (oldCapacity == MAX_ARRAY_SIZE)

            return;

        newCapacity = MAX_ARRAY_SIZE;

    }

    Entry<?,?>[] newMap = new Entry<?,?>[newCapacity]; 按照新容积创建新数组

    modCount++; 修改次数+1,用于实现遍历时的快速异常

    threshold = (int)Math.min(newCapacity * loadFactor, MAX_ARRAY_SIZE + 1); 按照新容积值获取扩容阈值

    table = newMap; 将新数组赋值给存储数据的数组属性

    for (int i = oldCapacity ; i-- > 0 ;) {  从老数组中将数据拷贝到新数组中

        for (Entry<K,V> old = (Entry<K,V>)oldMap[i] ; old != null ; ) {

            Entry<K,V> e = old;

            old = old.next;

            int index = (e.hash & 0x7FFFFFFF) % newCapacity;

            e.next = (Entry<K,V>)newMap[index];

            newMap[index] = e;

        }

    }

}





HashMap与HashTable区别

1、线程安全:HashMap是非线程安全的,HashTable是线程安全的;HashTable内部的方法基本都经过synchronized修饰。如果要保证线程安全的话就建议使用ConcurrentHashMap,也可以通过Collections.synchronizedMap(hashmap)方式将hashmap转换为线程安全的map

2、效率:因为线程安全的问题,HashMap要比HashTable效率高一点。另外HashTable基本被淘汰,不要在代码中使用它

3、对Null key和Null value的支持:HashMap中null可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为null。但是在HashTable中put进的键值只要有一个null,直接抛NullPointerException。 

4、初始容量大小和每次扩充容量大小的不同 : 

①创建时如果不指定容量初始值,Hashtable 默认的初始大小为11,之后每次扩充,容量变为原来的2n+1。HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。

②创建时如果给定了容量初始值,那么Hashtable会直接使用给定的大小,而HashMap会将其扩充为2的幂次方大小。也就是说HashMap总是使用2的幂作为哈希表的大小。 

5、底层数据结构:JDK1.8以后的HashMap在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认8)时,将链表转化为红黑树,以减少搜索时间。Hashtable没有这样的机制。 

推荐使用:在Hashtable的类注释可以看到,Hashtable是保留类不建议使用,推荐在单线程环境下使用HashMap替代,如果需要多线程使用则用ConcurrentHashMap替代。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值