MAP基础知识

344 阅读4分钟

JAVA 集合概览

image-20210222184640480

Map 接口

HashMap和Hashtable区别:

  • 线程是否安全: HashMap是非线程安全的,HashTable是线程安全的,因为HashTable内部的方法基本都经过synchronize修饰.如果保证线程安全建议使用ConcurrentHashMap

  • 效率: 因为线程安全的问题,HashMap要比HashTable效率高一点.另外HashTable基本淘汰,强烈不建议在代码中使用

  • 对Null Key 和 Null Value的支持: HashMap可以存储null的key和value,但null作为key只能出现一个,null可以作为多个值;HashTable不允许有null键和null值,否则会抛出 NPE(NullPointerException)

  • 初始容量大小和每次扩容量大小的不同:

    1. 创建时如果不指定初始容量,HashTable默认的初始大小为11,之后每次扩容,容量变为原来的2n+1;

      HasHMap默认的初始值大小为16.之后每次扩容,容量变为原来的2倍数

    2. 创建时如果给定了容量的初始值,那么HashTable会直接使用你==给定的大小==

      HashMap会将其扩充为==2的幂次方大小== HashMap中的tableSizeFor()方法保证.

    总结: HashMap总是使用2的幂作为哈希表的大小

  • 底层数据结构:

    • JDK1.8以后的HashMap在解决哈希冲突时有了较大的变化,当链表长度大于==阈值(默认值)== 将链表转换红黑树前会判断,如果当前数组的长度小于64,那么会选择先进行数组扩容,而不是转换为红黑树 将链表转化为红黑树,以减少搜索时间
    • Hashtable没用这样的机制
  • HashMap 中带有初始容量的构造函数:

    /**
    构造一个具有指定初始容量和负载因子的空HashMap 。
    
    参数:
    initialCapacity –初始容量
    loadFactor –负载系数
    抛出:
    IllegalArgumentException如果初始容量为负或负载系数为正
    推断的注释:
    @ org.jetbrains.annotations.Contract(pure = true)
    */
    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);
            this.loadFactor = loadFactor;
            this.threshold = tableSizeFor(initialCapacity);
        }
    
  • 如何保证扩容时2的幂次方

    /**
         * Returns a power of two size for the given target capacity.
         * 对于给定的目标容量,返回两倍大小的幂。
         */
        static final int tableSizeFor(int cap) {
            int n = cap - 1;
            n |= n >>> 1;
            n |= n >>> 2;
            n |= n >>> 4;
            n |= n >>> 8;
            n |= n >>> 16;
            return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
        }
    

HashMap和HashSet的区别:

HashSet底层是基于HashMap实现的.

HashSet的源码非常少,因为除了==clone(),writerObject(),readObject()==是HashSet自己实现外.其它都是调用HashMap中的方法

HashMapHashSet
实现了Map接口实现了Set接口
存储键值对仅存储对象
调用put()先map中添加元素调用add()向Set中添加元素
HashMap使用键(key)计算HashCodeHashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,就要使用equals()方法用来判断对象的相等性

HashMap和TreeMap区别:

  • TreeMap和HashMap都继承了AbstractMap,但是需要注意的是TreeMap还实现了NacigableMap接口和SortedMap接口

image-20210222194728053

  • 实现了NacigableMap接口让TreeMap有了对集合内元素的==搜索的能力==

  • 实现SortedMap接口让TreeMap有了对集合中的元素根据键排序的能力.默认是按照key的升序排序,不过也可以指定排序的比较器

    import java.util.Comparator;
    import java.util.TreeMap;
    
    /**
     * 类名称:Demo <br>
     * 类描述:TODO <br>
     *
     * @author 张兆发
     * @version 1.0.0
     * @date 创建时间:2021/2/22  7:54 下午 <br>
     */
    public class Demo {
        private Integer age;
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public Demo(Integer age) {
            this.age = age;
        }
    
        public static void main(String[] args) {
          //方式一
            TreeMap<Demo,String> treeMap = new TreeMap<>(new Comparator<Demo>() {
                @Override
                public int compare(Demo o1, Demo o2) {
                    if(o1.getAge()> o2.getAge()){
                        return 1;
                    }
                    if(o1.getAge()< o2.getAge()){
                        return -1;
                    }
                    return 0;
                }
            });
           //方式二
          TreeMap<Demo, String> treeMap = new TreeMap<>((Demo1, Demo2) > {
            int num = Demo1.getAge() - Demo2.getAge();
            return Integer.compare(num, 0);
          });
            treeMap.put(new Demo(19),"demo1");
            treeMap.put(new Demo(141),"demo2");
            treeMap.put(new Demo(11),"demo3");
            treeMap.put(new Demo(3),"demo4");
            treeMap.put(new Demo(321),"demo5");
            treeMap.put(new Demo(1112),"demo6");
            treeMap.entrySet().stream().forEach(
                    data -> {
                        System.out.println(data.getValue());
                    }
            );
        }
    }
    
    
  • 综上,相比于 HashMap 来说 TreeMap 主要多了对集合中的元素根据键排序的能力以及对集合内元素 的搜索的能力。

HashSet如何检查重复:

  1. 当把对象加入HashSet时,HashSet会先计算对象的HashCode值判断对象加入的位置,同时也会与其它加入的对象的hashcode值作比较,如果没有相符的hashcode,HashSet会假设对象没有重复出现,但是如果发现有相同的hashcode值的对象,这时会调用equals()方法来检查hashcode相等的对象是否真的相同,如果相同HashSet就不会让加入操作成功
  2. hashCode()与equals()的规定
    1. 如果两个对象相等,则hashcode一定也相同
    2. 两个对象相等,对两个equals方法返回true
    3. 两个对象有相同的hashcode值,他们也不一定是相等的
    4. equals方法被覆盖过,则hashcode方法也必须被覆盖过