Map集合系列

一、Map结构

1、架构

在这里插入图片描述

2、接口

1、Map接口是所有实现类和其他接口的父接口(键值对的存储方式),提供了最基本的接口方法:容量、判断是否为空、增加(单个或者集合)、删除、获取键集合、获取值集合、entrySet迭代键值对、jdk1.8中添加的lambda表达式
2、SortedMap接口:增加了排序功能,定义了比较器(默认或者自定义)、提供了三个方法:
subMap(K fromKey, K toKey)获取在该范围内的SortedMap
headMap(K toKey)获取小于该值的
SortedMap tailMap(K fromKey)获取大于等于该值的SortedMap

所有SortedMap 实现类都应该提供 4 个“标准”构造方法:
(01) void(无参数)构造方法,它创建一个空的有序映射,按照键的自然顺序进行排序。
(02) 带有一个 Comparator 类型参数的构造方法,它创建一个空的有序映射,根据指定的比较器进行排序。
(03) 带有一个 Map 类型参数的构造方法,它创建一个新的有序映射,其键-值映射关系与参数相同,按照键的自然顺序进行排序。
(04) 带有一个 SortedMap 类型参数的构造方法,它创建一个新的有序映射,其键-值映射关系和排序方法与输入的有序映射相同。无法保证强制实施此建议,因为接口不能包含构造方法。

3、NavigableMap接口:
所有关于获取小于的键值对或者key,都是获取小于中的最大值
所有关于获取大于的键值对或者key,都是获取大于中的最小值

1、提供操作键-值对的方法。
lowerEntry、floorEntry、ceilingEntry 和 higherEntry 方法,它们分别返回与小于、小于等于、大于等于、大于给定键的键关联的 Map.Entry 对象。
firstEntry、pollFirstEntry、lastEntry 和 pollLastEntry 方法,它们返回和/或移除最小和最大的映射关系(如果存在),否则返回 null。
2、提供操作键的方法。这个和第1类比较类似
lowerKey、floorKey、ceilingKey 和 higherKey 方法,它们分别返回与小于、小于等于、大于等于、大于给定键的键。
3、获取键集。
navigableKeySet、descendingKeySet分别获取正序/反序的键集。
4、获取键-值对的子集。

3、抽象类

1、AbstractMap实现了Map公共类方法:实现了部分方法,除了entrySet()返回键值对的方式(至于为什么没有实现,我还没想到解释)和put()每个map实现类插入方式不同,必须重写此类的 put 方法(否则将抛出 UnsupportedOperationException)
2、Dictionary:包括了基本的增删改查,但是这些方法都需要重写

4、子类

1、HashTable:继承自Dictionary,jdk1.1就发布,线程安全地存储键值对
2、HashMap:继承自AbstractMap,同样可以实现HashTable中的功能,唯一一点遗憾就是只适合于单线程,多线程环境可以通过静态方法synchronizedMap实现线程安全(该方法以后有机会补充),或者使用ConcurrentHashMap替代
3、TreeMap:底层实现红黑树,能够实现键值对的排序,针对排序的键值对有着得天独厚的优势
4、WeakHashMap:消除陈旧对象,即在我们使用短时间内就过期的缓存时最好使用weakHashMap,它包含了一个自动调用的方法expungeStaleEntries,这样就会在值被引用后直接执行这个隐含的方法,将不用的键清除掉。弱引用需要配合一个引用队列,将JVM回收的对象引用放置到队列中
5、LInkedHashMap:保证hashMap集合有序,存入的顺序与取出的顺序一致。

二、HashMap

我的其他博客已经说明过:https://blog.csdn.net/qq_32679835/article/details/90447248

三、HashTable

1、属性

1、Entry<?,?>[] table用来存储元素,默认初始值为11,下一次扩容为原来2倍+1
2、int count:总数
3、int threshold:扩容界限
4、float loadFactor:加载因子,默认为0.75
5、int modCount = 0:用于实现fail-fast机制

2、构造方法

1、 Hashtable(int initialCapacity, float loadFactor) 设置初始容量与加载因子
2、Hashtable(Map<? extends K, ? extends V> t)传入Map作为加载因子
3、其余两个构造函数都是默认调用1的构造函数

3、核心方法

HashTable中键值对不容许为null,设置为null便会抛出NullPointerException,需要关注的是所有方法都是线程安全

1、put操作与扩容操作,扩容为原来的2倍+1

//对外方法
public synchronized V put(K key, V value) {
        // 值不为空
        if (value == null) {
            throw new NullPointerException();
        }
        Entry<?,?> tab[] = table;
        int hash = key.hashCode();//计算hash使用默认计算方式
        int index = (hash & 0x7FFFFFFF) % tab.length;//查找数组中的位置
        @SuppressWarnings("unchecked")
        Entry<K,V> entry = (Entry<K,V>)tab[index];
        //key值已经存在,则替换旧值
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                V old = entry.value;
                entry.value = value;
                return old;
            }
        }
        addEntry(hash, key, value, index);
        return null;
    }
    //私有方法
    private void addEntry(int hash, K key, V value, int index) {
        modCount++;
        Entry<?,?> tab[] = table;
        if (count >= threshold) {
            rehash(); // 扩容
            tab = table;
            hash = key.hashCode();
            index = (hash & 0x7FFFFFFF) % tab.length;
        }
        // 创建新的实体
        @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>) tab[index];
        tab[index] = new Entry<>(hash, key, value, e);
        count++;
    }
     @SuppressWarnings("unchecked")
    protected void rehash() {
        int oldCapacity = table.length;
        Entry<?,?>[] oldMap = table;
        //扩容为原来2倍+1
        int newCapacity = (oldCapacity << 1) + 1;
        if (newCapacity - MAX_ARRAY_SIZE > 0) {
            if (oldCapacity == MAX_ARRAY_SIZE)//大于最大容量则数组容量等于最大容量
                // Keep running with MAX_ARRAY_SIZE buckets
                return;
            newCapacity = MAX_ARRAY_SIZE;
        }
        Entry<?,?>[] newMap = new Entry<?,?>[newCapacity];
        modCount++;
        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;
            }
        }
    }

2、putAll:添加一个Map集合进Map

 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());//迭代内部调用put方法
    }

3、get(Object key)获取key值对应的value

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

4、remove:删除Hashtable中键为key的元素

4、遍历方式

1、keySet()返回key值集合

public Set<K> keySet() {
        if (keySet == null)
            keySet = Collections.synchronizedSet(new KeySet(), this);//新建内部类KeySet
        return keySet;
    }
    private class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return getIterator(KEYS);//返回key值的迭代对象,通过Enumerator来迭代,遍历数组的方式是倒序的
        }
        public int size() {
            return count;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return Hashtable.this.remove(o) != null;
        }
        public void clear() {
            Hashtable.this.clear();
        }
    }

Enumerator来迭代,遍历数组的方式是倒序的

int index = table.length;//数组长度
public boolean hasMoreElements() {
            Entry<?,?> e = entry;
            int i = index;
            Entry<?,?>[] t = table;
            /* Use locals for faster loop iteration */
            while (e == null && i > 0) {
                e = t[--i];//逆序遍历数组,正序遍历数组所对应的链表
            }
            entry = e;
            index = i;
            return e != null;
        }

2、values()遍历Map中的所有值

public Collection<V> values() {
        if (values==null)
        //通过ValueCollection实现遍历,使用synchronizedCollection同步方法,内部同样通过Enumerator来迭代,遍历数组的方式是倒序的
            values = Collections.synchronizedCollection(new ValueCollection(),
                                                        this);
        return values;
    }

3、entrySet()遍历键值对:

 public Set<Map.Entry<K,V>> entrySet() {
        if (entrySet==null)
            entrySet = Collections.synchronizedSet(new EntrySet(), this);//内部同样使用Enumerator遍历
        return entrySet;
    }

4、elements获取所有值的Enumerator枚举类(属于土著方法,最直接调用枚举迭代)

5、总结

首先,Hashtable底层是通过数组加链表实现的
其次,Hashtable是不允许key或者value为null的。
并且,Hashtable的计算索引方法(int index = (hash & 0x7FFFFFFF) % tab.length;),默认容量大小(default = 11),扩容方法都与HashMap不太一样(两层循环扩容)。
其实我们可以看到,Hashtable之所以线程安全,大部分方法都是使用了synchronized关键字,虽然JDK优化了synchronized,但在方法上使用该关键字,无疑仍旧是效率低下的操作。

四、TreeMap

由于TreeMap篇幅过长,单独写了另一篇博客:https://blog.csdn.net/qq_32679835/article/details/93502092

五、WeakHashMap

WeakHashMap 也是一个散列表,它存储的内容也是键值对(key-value)映射,而且键和值都可以是null;其中的key值是弱键,也就意味着该键映射的键值对可以被JVM回收
弱键是通过WeakReference和ReferenceQueue实现的
1、新建WeakHashMap,将“键值对”添加到WeakHashMap中。
2、当“弱键”不再被其他对象引用,并被GC回收,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
3、下一次操作WeakHashMap前,同步table和queue队列,删除table中被GC回收的键值对

1、属性

1、默认初始容量:DEFAULT_INITIAL_CAPACITY = 16
2、隐式最大容量:MAXIMUM_CAPACITY = 1 << 30 (2^n<最大容量)
3、引用队列用来记录GC的键值对: ReferenceQueue queue = new ReferenceQueue<>()
其余属性与正常Map相同
4、private static final Object NULL_KEY = new Object();
由于WeakHashMap容许null键,此时可能会被GC回收,避免被回收的危险,将null键统一替换为NULL_KEY
在这里插入图片描述

2、构造函数

// 默认构造函数。
WeakHashMap()

// 指定“容量大小”的构造函数
WeakHashMap(int capacity)

// 指定“容量大小”和“加载因子”的构造函数
WeakHashMap(int capacity, float loadFactor)//每一次扩容为原来的2倍

// 包含“子Map”的构造函数
WeakHashMap(Map<? extends K, ? extends V> map)

3、核心方法

1、indexFor()定位桶位置与HashMap相同

 private static int indexFor(int h, int length) {
        return h & (length-1);
    }

2、expungeStaleEntries()遍历引用队列,同步table中的键值对

private void expungeStaleEntries() {
        for (Object x; (x = queue.poll()) != null; ) {
            synchronized (queue) {
                @SuppressWarnings("unchecked")
                    Entry<K,V> e = (Entry<K,V>) x;
                int i = indexFor(e.hash, table.length);

                Entry<K,V> prev = table[i];
                Entry<K,V> p = prev;
                while (p != null) {
                    Entry<K,V> next = p.next;
                    if (p == e) {
                        if (prev == e)
                            table[i] = next;
                        else
                            prev.next = next;
                        e.value = null; // Help GC
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }

3、get()获取对应key值的value值

public V get(Object key) {
        Object k = maskNull(key);
        int h = hash(k);//hash计算
        Entry<K,V>[] tab = getTable();
        int index = indexFor(h, tab.length);//数组位置
        Entry<K,V> e = tab[index];
        while (e != null) {
            if (e.hash == h && eq(k, e.get()))
                return e.value;
            e = e.next;
        }
        return null;
    }

4、put()添加键值对

public V put(K key, V value) {
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int i = indexFor(h, tab.length);
		// 若“该key”对应的键值对已经存在,则用新的value取代旧的value。然后退出!
        for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
            if (h == e.hash && eq(k, e.get())) {
                V oldValue = e.value;
                if (value != oldValue)
                    e.value = value;
                return oldValue;
            }
        }
		// 若“该key”对应的键值对不存在于WeakHashMap中,则将“key-value”添加到table中
        modCount++;
        Entry<K,V> e = tab[i];
        tab[i] = new Entry<>(k, value, queue, h, e);
        if (++size >= threshold)
            resize(tab.length * 2);//扩容为原来两倍,重新计算元素所在的桶位置
        return null;
    }

5、remove()删除键为key的元素

public V remove(Object key) {
        Object k = maskNull(key);
        int h = hash(k);
        Entry<K,V>[] tab = getTable();
        int i = indexFor(h, tab.length);
        Entry<K,V> prev = tab[i];
        Entry<K,V> e = prev;

        while (e != null) {
            Entry<K,V> next = e.next;
            if (h == e.hash && eq(k, e.get())) {
                modCount++;
                size--;
                if (prev == e)
                    tab[i] = next;
                else
                    prev.next = next;//删除链表的节点
                return e.value;
            }
            prev = e;
            e = next;
        }

        return null;
    }

4、迭代方式

1、entrySet()迭代

Iterator iter = map.entrySet().iterator();
while(iter.hasNext()) {
    Map.Entry entry = (Map.Entry)iter.next();
    // 获取key
    key = entry.getKey();
        // 获取value
    integ =entry.getValue();
}

2、keySet()迭代
3、values()迭代

5 使用demo

可以用作缓存等,内存不足的时候回收

import java.util.WeakHashMap;

public class testWeakHashMap {
	public static void main(String[] args) {
		WeakHashMap<String, String> map = new WeakHashMap<String, String>();
		WeakHashMap<String, String> map2 = new WeakHashMap<String, String>();
		map.put(new String("s"), "robin");
		map2.put("s", "robin");
		System.gc();
		System.out.println(map);
		System.out.println(map2);//常量池不会被回收
	}

}

六、Map总结

1、HashMap和Hashtable异同

  • 相同点

1、table存储结构,采取数组+链表的存储形式,添加数据与删除数据步骤:计算Hash码,之后计算对应数组索引,之后遍历遍历链表插入元素
2、都实现了实现了Map、Cloneable、java.io.Serializable接口,可以被克隆对象与序列化

  • 不同点

1、继承方式不同:HashMap 继承于AbstractMap,Hashtable 继承于Dictionary,AbstractMap是一个抽象类,它实现了Map接口的绝大部分API函数,而Hashtable 继承于Dictionary,最大不同在于支持Enumeration遍历
2、线程安全方式:HashMap 非同步,不是线程安全;Hashtable 同步线程安全
3、null值处理:HashMap的key、value都可以为null。Hashtable的key、value都不可以为null。
4、支持遍历方式:HashMap只支持Iterator(迭代器)遍历。而Hashtable支持Iterator(迭代器)和Enumeration(枚举器)两种方式遍历,其实HashTable内部迭代,还是封装了枚举器的遍历方法
5、通过Iterator迭代器遍历时,遍历的顺序不同:HashMap是“从前向后”的遍历数组;再对数组具体某一项对应的链表,从表头开始进行遍历;Hashtabl是“从后往前”的遍历数组;再对数组具体某一项对应的链表,从表头开始进行遍历。
6、容量的初始值 和 增加方式都不一样:HashMap默认的容量大小是16;增加容量时,每次将容量变为“原始容量x2”;Hashtable默认的容量大小是11;增加容量时,每次将容量变为“原始容量x2 + 1”。
7、添加key-value时的hash值算法不同:HashMap自定义Hash方法,HashTable直接采用的key的hashCode()。
HashMap自定义hash方法,添加了hash扰乱:
在这里插入图片描述

2、HashMap和WeakHashMap异同

  • 相同点

1 它们都是散列表,存储的是“键值对”映射。
2 它们都继承于AbstractMap,并且实现Map基础。
3 它们的构造函数都一样。 它们都包括4个构造函数,而且函数的参数都一样。
4 默认的容量大小是16,默认的加载因子是0.75。
5 它们的“键”和“值”都允许为null。
6 它们都是“非同步的”。

  • 不同点

1、HashMap实现了Cloneable和Serializable接口,而WeakHashMap没有
2、HashMap的“键”是“强引用(StrongReference)”,而WeakHashMap的键是“弱引用(WeakReference)

3、HashMap和TreeMap的区别

1、底层实现方式:HashMap是Hash表,TreeMap是红黑树
2、是否有序:HashMap无序,TreeMap根据键排序
3、适用场景:HashMap:Map中添加、删除和查找元素;TreeMap适用于按照自然排序或者自定义排序遍历键
4、HashMap通常会比treeMap快一点

4、使用场景

1、需要基于排序的统计功能:TreeMap底层采用红黑树,时间复杂度为O(logn)
2、需要快速增删改查的存储功能:hashMap,时间复杂度为O(1)
3、需要快速增删改查而且需要保证遍历和插入顺序一致的存储功能 ,LinkedhashMap 时间复杂度O(1),并且保证按照插入顺序

七、引用

【1】Map系列文章:https://www.cnblogs.com/skywang12345/p/3323085.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值