Java集合之HashMap,Hashtable

HashMap和HashTable是都是Map的实现类。

一. HashMap

在学习HashMap之前,首先要对哈希表这种数据结构有所了解。

哈希表(Hash table,也叫散列表),是根据关键码值(Key value)而直接进行访问的数据结构。也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表。

哈希表的构造函数:

直接寻址法:f(key)=key

当两个key相等时,会产生哈希冲突

除留余数法:f(p)=p%n(n<m) n的选取关键,卡槽的数量m要大于n

也会发生哈希冲突。

哈希冲突如何解决呢解决?这里介绍两种方法,分别是链地址法和探测法。

链地址法:用数组+单项链表的方法来解决哈希冲突

 探测法:pos(n) = f(n)+p(n)
             线性探测法:p(n)=1 -> p(n)=2 ->.......直到找到卡槽里有位置
             随机探测法:p(n)= random() 随机找一个卡槽是否有值

 一.HashMap

HashMap就是基于哈希表这种数据结构实现的,它存放的是key-value键值对。

1.HashMap的类声明

public class HashMap<K,V>
    extends AbstractMap<K,V>
implements Map<K,V>, Cloneable, Serializable

HashMap继承自Map下的AbstracyMap,实现了Map接口,Cloneable接口,Serializable接口。

//为什么是16?因为必须要满足2的次方问题
static final int DEFAULT_INITIAL_CAPACITY = 16;//默认的容量大小

static final int MAXIMUM_CAPACITY = 1 << 30;//最大的容量大小

static final float DEFAULT_LOAD_FACTOR = 0.75f;//加载因子

transient Entry[] table;//HashMap底层是一个表

transient int size;//当前存储数据数量

int threshold;//阈值

transient volatile int modCount;//HashpMap被改变的次数

final float loadFactor;//加载因子

2.HashMap构造函数

①带参构造函数

指定容量和加载因子

public HashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        // 保证容量是2的次方
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;

        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        table = new Entry[capacity];
        init();
}

②无参构造函数

构造函数容量大小是默认的16,加载因子是默认的0.75
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
}

3.主要方法

①get():获得key对应的value值

public V get(Object key) {
        if (key == null)//如果key为空
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }

private V getForNullKey() {
        if (size == 0) {//当前HashMap中没有元素
            return null;
        }
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {//遍历table[0]链表,找到key                               为null的
            if (e.key == null)
                return e.value;
        }
        return null;
    }

②containsKey():是否包含某个key

// HashMap是否包含key
    public boolean containsKey(Object key) {
        return getEntry(key) != null;
   }

③getEntry():返回键值为key的键值对

final Entry<K,V> getEntry(Object key) {
        if (size == 0) {//无元素
            return null;
        }
        // HashMap将“key为null”的元素存储在table[0]位置,“key不为null”的则调用hash()计算哈希值
        int hash = (key == null) ? 0 : hash(key);
         // 在hash值对应的链表”上查找键值等于key的元素
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            //比较哈希值和key值是否相等
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

④put():添加元素

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        // 若“key为null”,则将该键值对添加到table[0]中。
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key);//计算哈希值
        int i = indexFor(hash, table.length);//找索引
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            // 若“该key”对应的键值对已经存在,则用新的value取代旧的value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        // 若“该key”对应的键值对不存在,则将“key-value”添加到table中
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

⑤addEntry():添加一个节点

⑥remove()

// 删除“键为key”元素
     public V remove(Object key) {
         Entry<K,V> e = removeEntryForKey(key);
         return (e == null ? null : e.value);//返回该key对应的value值
     }

4.HashMap的三种遍历方式

①通过键值对遍历

Iterator<Map.Entry<String, String>> iterator2 = hashMap.entrySet().iterator();
		while(iterator2.hasNext()){
			Map.Entry<String, String> next = iterator2.next();
			String s1 = next.getKey();
			String s2 = next.getValue();
			System.out.println(s1+":::"+s2);
		}

②通过键遍历

Iterator iterator3 = hashMap.keySet().iterator();
		while(iterator3.hasNext()){
			System.out.println(iterator3.next());
		}

③通过值遍历

Iterator iterator4 = hashMap.values().iterator();
		while(iterator4.hasNext()){
			System.out.println(iterator4.next());
		}

5.例题

十万个数据的重复统计问题
        1.十万个数据如何存储     
            用ArrayList 或 LinkedList???
            需要用到ArrayList有参构造函数
        2.数据统计
            用HashMap
            key:存放数据
            val:存放次数

以下为代码:

/**
		 * 用集合存储十万个数据
		 */
		ArrayList<Integer> list1 = new ArrayList<Integer>(10000);
		Random random = new Random();
		for(int i =1;i<=10000;i++){
			list1.add(random.nextInt(9));
		}
		
		HashMap<Integer, Integer> hashMap = new HashMap<Integer,Integer>();
		/**
		 * 用迭代器对ArrayList遍历
		 */
		Iterator<Integer> iterator2 = list1.iterator();
		
		while(iterator2.hasNext()){
			int tmp = iterator2.next();
			if(!hashMap.containsKey(tmp)){//若HashMap中没有这个值
				hashMap.put(tmp, 0);
			}else{
				hashMap.put(tmp,hashMap.get(tmp)+1);//修改HashMap的value,使其+1
			}
		}
		/**
		 * 键值对遍历
		 */
		Iterator<Map.Entry<Integer, Integer>> iterator = hashMap.entrySet().iterator();
		while(iterator.hasNext()){
			Map.Entry<Integer, Integer> next = iterator.next();
			Integer i1 = next.getKey();
			Integer i2 = next.getValue();
			System.out.println(i1+":"+i2);
		}

二. Hashtable

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

Hashtable继承自Dictionary,实现了Map,Cloneable和Serializable接口。

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

1.Hashtable的构造函数

指定初始容量和加载因子

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);
        initHashSeedAsNeeded(initialCapacity);
    }
// 指定“容量大小”的构造函数
public Hashtable(int initialCapacity)
 //默认构造函数
public Hashtable() {
        this(11, 0.75f);
    }

2.主要方法

Hashtable里也提供了put,get,remove方法,与HashMap类似,此处不做详解。

三. HashMap和Hashtable异同点

  相同点

1.HashMap和HashTable都是存储键值对的散列表

键值对通过table数组来存储,数组中的每个数都是一个entry节点,每个entry后面又连了一个单向链表。

HashMap

 

   

HashTable

2.添加元素时,判断key值是否相等的标准一致

HashTable

HashMap

 

不同点

1.继承和实现接口不同

HashMap:

可以从源码中看出,HashMap继承自AbstractMap,实现了Cloneable,Map,Serializable(序列化)接口。

 

HashTable:

HsahTable则继承自Dictionary,实现了Map,Cloneable还有java.io.Serializable接口。

 

可以看出,HashMap和HashTable都实现了Map,Cloneable、java.io.Serializable接口,

但继承方式却不同。

AbstractMap是一个抽象类,实现了Map的绝大多数方法,Dictionary直接继承于Object类,里面的函数比Map的少。

2.线程安全不同

HashMap不是线程安全的,HashTable是线程安全的,支持多线程。

HashTable:

 

可以看出HashTable里的方法都加了锁,运行到这个方法时,需要检查有没有其他线程正在使用这个方法。

3.键和值的允许null情况不同

HashMap允许键值为null

HashTable不允许键和值为null

HashMap的添加键值对方法:

HashTable添加方法

4.初始容量大小不同

HashMap:16

        

HashTable:11

5.添加键值对时,计算哈希值方法不同

HashMap:没有用hashCode(),而是自定义的计算哈希值

HashTable:使用了key的hashCode值

6.HashMap效率高,HashTable效率低

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值