Map 接口常用实现类底层分析

一、Map 接口

        接下来讲的都是基于 jdk8 来开展的。

1.1 特点

        1、Map Collection 并列存在。Map 是用于保存具有映射关系的数据,即 key-value

        2、Map 中的 key value 可以是任何引用类型的数据类型。

        3、Map 中的 key 不允许重复,原因和 HashSet 一样。

        4、Map 中的 value 是可以重复的。

        5、Map 中的 key 可以为 nullvalue 也可以为 null,注意 key null 时只能有一个,value null 时可以有多个。

        6、常用 String 类作为 Map key

        7、key value 之间存在单向一对一关系,即通过指定的 key 总能找到对于的 value

        8、Map 存放数据的 key-value 示意图,一对 key-value 是放在一个 Node 中的,又因为 Node 实现了 Entry 接口,也有人说是一对 key-value 就是一个 Entry

1.2 常用实现类

        HashMapHashTableSortedMap(接口)、TreeMap LinkedHashMapProperties 等。

1.3 常用方法

public class TestMap {
    public static void main(String[] args) {
        Map map = new HashMap<>();

        // put 添加元素
        map.put("孙悟空","唐僧");
        map.put("孙悟空","猪八戒");
        map.put("宋江","潘金莲");
        map.put("武大郎","西门庆");
        map.put("曹操",null);
        map.put(null,"荀彧");

        // get 根据 key 获取元素
        Object o = map.get("武大郎");
        // 根据 key 删除元素
        map.remove("宋江");
        // 判断 key 是否存在
        boolean b = map.containsKey(null);
        System.out.println(b);
        // 获取 map 的元素个数
        System.out.println(map.size());
        // 判断 map 元素个数是否为 0
        System.out.println(map.isEmpty());
        // 清除 map
        map.clear();

    }
}

1.4 遍历方式

1.4.1 keySet 方式

public static void main(String[] args) {
	Map map = new HashMap<>();
	map.put("孙悟空","唐僧");
	map.put("武大郎","西门庆");

	// 先取出所有的 key,然后再取出 value
	Set keySet = map.keySet();
	// (1) 使用增强 for 循环
	for (Object obj :keySet) {
		System.out.println(obj+"-"+map.get(obj));
	}
	System.out.println("-----------------------");
	// (2) 迭代器
	Iterator iterator = keySet.iterator();
	while (iterator.hasNext()) {
		Object next =  iterator.next();
		System.out.println(next+"-"+map.get(next));
	}
}

1.4.2 value 值方式

public static void main(String[] args) {
	Map map = new HashMap<>();
	map.put("孙悟空","唐僧");
	map.put("武大郎","西门庆");

	// 只能取出所有的 values
	Collection values = map.values();
	// (1) 增强 for 循环
	for (Object obj :values) {
		System.out.println(obj);
	}
	System.out.println("-----------------------");
	//(2)迭代器
	Iterator iterator1 = values.iterator();
	while(iterator1.hasNext()){
		Object next = iterator1.next();
		System.out.println(next);
	}
}

1.4.3 EntrySet 方式

public static void main(String[] args) {
	Map map = new HashMap<>();
	map.put("孙悟空","唐僧");
	map.put("武大郎","西门庆");
	
	Set set = map.entrySet();
	// (1) 增强 for 循环
	for (Object entry :set) {
		// 将 entry 转换成 Map.Entry
		Map.Entry m = (Map.Entry)entry;
		System.out.println(m.getKey()+"-"+m.getValue());
	}
	System.out.println("-----------------------");
	// (2) 迭代器
	Iterator iterator2 = set.iterator();
	while (iterator2.hasNext()) {
		Map.Entry m=  (Map.Entry) iterator2.next();
		System.out.println(m.getKey()+"-"+m.getValue());
	}
}

1.5 习题练习

        使用 HashMap 添加 3 个员工对象,要求:键为员工 id,值为员工对象。并遍历显示工资 > 18000 员工(遍历方式最少两种)。员工类:姓名、工资、员工 id

class Employee{
    private String id;
    private String name;
    private double salary;

    public Employee(String id, String name, double salary) {
        this.id = id;
        this.name = name;
        this.salary = salary;
    }
   // setter、getter、toString

    @Override
    public String toString() {
        return "Employee{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", salary=" + salary +
                '}';
    }
}
public static void main(String[] args) {

	Map map = new HashMap();
	Employee e1 = new Employee("1","张三",20000);
	Employee e2 = new Employee("2","李四",8000);
	Employee e3 = new Employee("3","王五",60000);
	map.put(e1.getId(),e1);
	map.put(e2.getId(),e2);
	map.put(e3.getId(),e3);

	Set set = map.keySet();
	for (Object key :set) {
		Employee employee = (Employee)map.get(key);
		if(employee.getSalary()>18000){
			System.out.println(key+"-"+employee);
		}
	}
	System.out.println("--------------");
	Iterator iterator = set.iterator();
	while (iterator.hasNext()) {
		Object key =  iterator.next();
		Employee employee = (Employee)map.get(key);
		if(employee.getSalary()>18000){
			System.out.println(key+"-"+employee);
		}
	}
	System.out.println("--------------");
	Set set1 = map.entrySet();
	for (Object entrySet :set1) {
		Map.Entry entry =  (Map.Entry)entrySet;
		Employee employee = (Employee) entry.getValue();
		if(employee.getSalary()>18000){
			System.out.println(entry.getKey()+"-"+employee);
		}
	}
}

二、HashMap

2.1 特点

        1、HashMap Map 接口使用频率最高的实现类

        2、HashMap 是以 key-value 对的形式存储数据(HashMap$Node 类型)

        3、 key 不能重复,但是值可以重复,允许使用 null 键和 null

        4、如果添加相同的 key ,则会覆盖原来的 key-value ,等同于修改(key 不会替换,value 会替换)

        5、HashSet 一样,不保证映射的顺序,因为底层是以 hash 表的方式存储的(jdk8 HashMap 底层为 数组+链表+红黑树)

        6、HashMap 没有实现同步,因此是线程不安全的。方法没有做同步互斥的操作,没有 synchronized。

2.2 底层机制

        1、无论是数组里面、链表里面还是红黑树里面存储的 key-value 元素,都是 HashMap$Node 的对象,而 HashMap$Node 类实现了 HashMap$Entry 接口

        2、jdk1.7HashMap 底层实现为 数组+链表 的形式,jdk1.8 HashMap 底层实现为 数组+链表+红黑树

        3、jdk1.7 HashMap 当给链表添加元素的时候,它添加到链表的头部。而 jdk1.8 HashMap 当给链表添加元素的时候,它会添加到链表的尾部。

2.3 扩容机制

        扩容机制和 HashSet 完全一致。

        1、HashMap 底层维护了 Node 类型的数组 table,默认为 null

        2、当创建对象的时候,将加载因子初始化为 0.75

        3、当添加 key-value 时,通过 key 的哈希值得到在 table 的索引。然后判断该索引处是否有元素,如果没有元素直接添加。如果该索引处有元素,继续判断该元素的 key 和准备加入的 key 是否相等,如果相等,则直接替换 value;如果不相等则需要判断是树结构还是链表结构,做出相应处理,如果添加时发现容量不够,则需要扩容。

        4、第一次添加,则需要扩容 table 容量为 16,临界值为 1216*0.75

        5、以后再扩容,则需要扩容 table 容量为原来的 2 倍,临界值也为原来的 2 倍,即 24,依此类推。

        6、在 java8 中,如果一条链表的元素个数到达 TREEIFY_THRESHOLD (默认是 8 ) 并且 table 数组的大小 >= MIN_TREEIF_CAPACITY(默认是 64),就会进行树化,否则仍然采用数组的扩容机制。

三、Hashtable

3.1 特点

        1、Hashtable 的键和值都不能为 null,否则会抛空指针异常。

        2、Hashtable 的使用方法基本和 HashMap 一样。

        3、Hashtable 是线程安全的,HashMap 是线程不安全的。

3.2 底层机制

        1、底层是数组,类型为 Hashtabl$Entry,初始大小为 11

        2、临界值为 11*0.75 = 8,加载因子也是 0.75

3.3 扩容机制

        初始容量为 11,当容量到达 8 个的时候,就会触发扩容机制,第二次扩容之后的大小为 23,所以它的扩容机制为:2n+1n 为现在的容量。

3.4 Hashtable 和 HashMap 对比

版本线程安全效率允许 null 键和 null 值
HashMap1.2不安全可以
Hashtable1.0安全较低不可以

四、Properties

4.1 特点

        1、Properties 类继承自 Hashtable 类并实现了 Map 接口,也是使用一种键值对的形式来保存数据。

        2、它的使用特点和 Hashtable 类似,不能存放 null 键和 null

        3、Properties 类还可以从 xxx.properties 文件中,加载数据到 Properties 类对象,并进行读取和修改。

        4、在工作中经常使用 xxx.properties 文件作为配置文件。

4.2 常用方法

public class PropertiesTest {
    public static void main(String[] args) {
        Properties properties = new Properties();
        // 新增
        properties.put("name","zhangSan");
        // properties.put(null,"zhangSan"); 抛出空指针异常
        // properties.put("age",null);抛出空指针异常
        properties.put("age",20);
        properties.put("address","北京市");
        // 修改
        properties.put("address","天津市");
        // 查看
        System.out.println(properties.get("age"));
        // 删除
        System.out.println(properties.remove("name"));
        System.out.println(properties);
    }
}

五、TreeMap

5.1 特点

5.1.1 无参构造

         当我们使用无参构造器创建 TreeMap 时,属于自然排序,当进行去重操作时,会调用 key 默认实现的 Comparable 类的 compareTo() 方法进行去重。

public class TreeMapTest {
    public static void main(String[] args) {
        TreeMap treeMap = new TreeMap();

        // 使用默认的无参构造器创建 TreeMap,是无序的
        treeMap.put("jack","杰克");
        treeMap.put("tom","汤姆");
        treeMap.put("kui","亏瑞斯");
        treeMap.put("smith","史密斯");
        System.out.println("TreeMap="+treeMap);
    }
}

5.1.2 有参构造

        TreeMap 提供了一个有参的构造器,可以传入一个比较器(匿名内部类)并指定排序规则对 key 进行排序。

public class TreeMapTest {
    public static void main(String[] args) {
        TreeMap treeMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                // 按照长度大小排序
                return ((String)o1).length()-((String)o2).length();
            }
        });
        treeMap.put("jack","杰克");
        treeMap.put("tom","汤姆");
        treeMap.put("kuisijf","亏瑞斯");
        treeMap.put("smith","史密斯");
        System.out.println("TreeMap="+treeMap);
    }
}

5.2 底层机制 

         1、有参构造器把实现了 Comparator 接口的匿名内部类对象传送给 TreeMap comparator 属性。

        2、调用 put 方法,第一次添加的时候,把 key-value 封装到 Entry 对象,放入 root 中。以后的添加,每次都遍历所有的 key,调用匿名内部类的 compare() 方法进行比较。  compare() 方法执行完毕后会返回一个 int 类型的结果,若结果小于 0 ,则该元素放在比较元素左边;若结果大于 0,则该元素放在比较元素右边;若结果等于 0,数据不添加进去。

5.3 习题演练

public class TreeMapTest {
    public static void main(String[] args) {
        TreeMap treeMap = new TreeMap(new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                // 按照长度大小排序
                return ((String)o1).length()-((String)o2).length();
            }
        });
        treeMap.put("jack","杰克");
        treeMap.put("tom","汤姆");
        treeMap.put("kuisijf","亏瑞斯");
        treeMap.put("smith","史密斯");
        treeMap.put("xhf","许海峰");// 加不进去,因为他的长度和 tom 一样
        System.out.println("TreeMap="+treeMap);
    }
}

六、ConcurrentHashMap

6.1 特点

        线程安全,在多线程的条件下,效率比 HashTable 高一些。

6.2 底层机制

        添加元素的时候,把这个容器分成了 16 段,每一次往里面插得时候,只锁定 16 段中的一个(16 是比较通用的,可以配),就是把这个锁给细化了。

        两个线程就可以同时并发的往里面插数据,就不需要锁定整个对象了。关键在于使用了锁分离技术。它使用了多个锁来控制对 hash 表的不同部分进行的修改。

        ConcurrentHashMap 内部使用段(Segment)来表示这些不同的部分,每个段其实就是一个小的 Hashtable,它们有自己的锁。只要多个修改操作发生在不同的段上,它们就可以并发进行。大量的利用了 volatile,final,CAS lock-free 技术来减少锁竞争对于性能的影响。

七、总结

八、Collections 常用方法介绍

8.1、简介

        Collections 是一个操作 SetList Map 等集合的工具类,它提供了一系列静态的方法对集合中的元素进行排序、查询和修改等操作。

8.2、操作方法

8.2.1 排序操作

public class CollectionsTest {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();

        list.add("tom");
        list.add("smith");
        list.add("king");
        list.add("milan");

        // 反转 list 中的元素
        Collections.reverse(list);
        System.out.println("list="+list);
        // 对 list 中的元素进行随机排序
        Collections.shuffle(list);
        System.out.println("list="+list);
        // 对 list 中的元素进行自然排序,就是按照字母的顺序排序
        Collections.sort(list);
        System.out.println("自然排序后"+list);

        // 可以自定义指定排序方法
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                return ((String)o1).length()-((String)o2).length();
            }
        });
        System.out.println("指定排序后"+list);
        // 将 list 集合中的 i 位置元素和 j 位置元素进行交换
        Collections.swap(list,0,1);
        System.out.println("交换后的情况:"+list);
    }
}

8.2.2 查找替换

public class CollectionsTest {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();

        list.add("tom");
        list.add("smith");
        list.add("king");
        list.add("milan");

        // 根据元素的自然排序,返回给定集合中的最大元素
        Collections.max(list);
        // 根据 Comparator 指定的顺序,返回给定集合中的最大元素
        Collections.max(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                // 返回长度最大的元素
                return ((String)o1).length()-((String)o2).length();
            }
        });
        // 根据元素的自然排序,返回给定集合中的最小元素
        Collections.min(list);
        // 根据 Comparator 指定的顺序,返回给定集合中的最小元素
        Collections.min(list, new Comparator() {
            @Override
            public int compare(Object o1, Object o2) {
                // 返回长度小的元素
                return ((String)o1).length()-((String)o2).length();
            }
        });
        // 返回指定集合中指定元素的出现次数
        int count = Collections.frequency(list,"tom");
        // 将 list 中的元素复制到 dest 集合中
        ArrayList<Object> dest = new ArrayList<>();
        // 为了完成一个完整的拷贝,我们需要先给 dest 赋值,大小和 list.size() 一样
        for(int i=0;i<list.size();i++){
            dest.add("");
        }
        Collections.copy(dest,list);
        System.out.println("dest="+dest);
        // 使用 newTom 替换 list 集合中的 tom
        Collections.replaceAll(list,"tom","newTom");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐的小三菊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值